From 2ecdeb3dddc1c47ba6fd25536cacca88ce659b34 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 21:54:08 -0800 Subject: [PATCH 01/57] add an inbox blob table --- db/statusnet.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/db/statusnet.sql b/db/statusnet.sql index 94b03df639..cb33ccf33e 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -596,3 +596,11 @@ create table user_location_prefs ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table inbox ( + + user_id integer not null comment 'user receiving the notice' references user (id), + notice_ids blob comment 'packed list of notice ids', + + constraint primary key (user_id) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; From 088ef9b427aae98ee937d9052b10ee9cd6c9e145 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 22:02:46 -0800 Subject: [PATCH 02/57] add inbox data class --- classes/Inbox.php | 51 +++++++++++++++++++++++++++++++++++++++++++ classes/statusnet.ini | 7 ++++++ 2 files changed, 58 insertions(+) create mode 100755 classes/Inbox.php diff --git a/classes/Inbox.php b/classes/Inbox.php new file mode 100755 index 0000000000..35f532c061 --- /dev/null +++ b/classes/Inbox.php @@ -0,0 +1,51 @@ +. + * + * @category Data + * @package StatusNet + * @author Evan Prodromou + * @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/ + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class Inbox extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'inbox'; // table name + public $user_id; // int(4) primary_key not_null + public $notice_ids; // blob + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Inbox',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 4077746c47..e4c6740abe 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -240,6 +240,13 @@ address = 130 address_type = 130 created = 142 +[inbox] +user_id = 129 +notice_ids = 66 + +[inbox__keys] +user_id = K + [invitation__keys] code = K From 775292eedeec794b69e2bf59185bdbc8b79ed157 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 22:03:06 -0800 Subject: [PATCH 03/57] flip exe bit --- classes/Inbox.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 classes/Inbox.php diff --git a/classes/Inbox.php b/classes/Inbox.php old mode 100755 new mode 100644 From 7640d3f07bad0710d69575efc7ceda115f24a60a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 30 Dec 2009 09:03:06 -1000 Subject: [PATCH 04/57] Replace Notice_inbox with Inbox --- classes/Notice_inbox.php | 184 --------------------------------------- scripts/inbox_users.php | 107 ----------------------- scripts/triminboxes.php | 56 ------------ 3 files changed, 347 deletions(-) delete mode 100644 classes/Notice_inbox.php delete mode 100755 scripts/inbox_users.php delete mode 100644 scripts/triminboxes.php diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php deleted file mode 100644 index b39006542c..0000000000 --- a/classes/Notice_inbox.php +++ /dev/null @@ -1,184 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; - -// We keep 5 pages of inbox notices in memcache, +1 for pagination check - -define('INBOX_CACHE_WINDOW', 101); -define('NOTICE_INBOX_GC_BOXCAR', 128); -define('NOTICE_INBOX_GC_MAX', 12800); -define('NOTICE_INBOX_LIMIT', 1000); -define('NOTICE_INBOX_SOFT_LIMIT', 1000); - -define('NOTICE_INBOX_SOURCE_SUB', 1); -define('NOTICE_INBOX_SOURCE_GROUP', 2); -define('NOTICE_INBOX_SOURCE_REPLY', 3); -define('NOTICE_INBOX_SOURCE_FORWARD', 4); -define('NOTICE_INBOX_SOURCE_GATEWAY', -1); - -class Notice_inbox extends Memcached_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice_inbox'; // table name - public $user_id; // int(4) primary_key not_null - public $notice_id; // int(4) primary_key not_null - public $created; // datetime() not_null - public $source; // tinyint(1) default_1 - - /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) - { - return Notice::stream(array('Notice_inbox', '_streamDirect'), - array($user_id, $own), - ($own) ? 'notice_inbox:by_user:'.$user_id : - 'notice_inbox:by_user_own:'.$user_id, - $offset, $limit, $since_id, $max_id, $since); - } - - function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) - { - $inbox = new Notice_inbox(); - - $inbox->user_id = $user_id; - - if (!$own) { - $inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY); - } - - if ($since_id != 0) { - $inbox->whereAdd('notice_id > ' . $since_id); - } - - if ($max_id != 0) { - $inbox->whereAdd('notice_id <= ' . $max_id); - } - - if (!is_null($since)) { - $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - - $inbox->orderBy('created DESC'); - - if (!is_null($offset)) { - $inbox->limit($offset, $limit); - } - - $ids = array(); - - if ($inbox->find()) { - while ($inbox->fetch()) { - $ids[] = $inbox->notice_id; - } - } - - return $ids; - } - - function &pkeyGet($kv) - { - return Memcached_DataObject::pkeyGet('Notice_inbox', $kv); - } - - static function gc($user_id) - { - $entry = new Notice_inbox(); - $entry->user_id = $user_id; - $entry->orderBy('created DESC'); - $entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX); - - $total = $entry->find(); - - if ($total > 0) { - $notices = array(); - $cnt = 0; - while ($entry->fetch()) { - $notices[] = $entry->notice_id; - $cnt++; - if ($cnt >= NOTICE_INBOX_GC_BOXCAR) { - self::deleteMatching($user_id, $notices); - $notices = array(); - $cnt = 0; - } - } - - if ($cnt > 0) { - self::deleteMatching($user_id, $notices); - $notices = array(); - } - } - } - - static function deleteMatching($user_id, $notices) - { - $entry = new Notice_inbox(); - return $entry->query('DELETE FROM notice_inbox '. - 'WHERE user_id = ' . $user_id . ' ' . - 'AND notice_id in ('.implode(',', $notices).')'); - } - - static function bulkInsert($notice_id, $created, $ni) - { - $cnt = 0; - - $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES '; - $qry = $qryhdr; - - foreach ($ni as $id => $source) { - if ($cnt > 0) { - $qry .= ', '; - } - $qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') "; - $cnt++; - if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { - // FIXME: Causes lag in replicated servers - // Notice_inbox::gc($id); - } - if ($cnt >= MAX_BOXCARS) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - $qry = $qryhdr; - $cnt = 0; - } - } - - if ($cnt > 0) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - } - - return; - } -} diff --git a/scripts/inbox_users.php b/scripts/inbox_users.php deleted file mode 100755 index 32adcea213..0000000000 --- a/scripts/inbox_users.php +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env php -. - */ - -# Abort if called from a web server - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$helptext = << - -Update users to use inbox table. Listed in an ID file, default 'ids.txt'. - -ENDOFHELP; - -require_once INSTALLDIR.'/scripts/commandline.inc'; - -$id_file = (count($args) > 1) ? $args[0] : 'ids.txt'; - -common_log(LOG_INFO, 'Updating user inboxes.'); - -$ids = file($id_file); - -foreach ($ids as $id) { - - $user = User::staticGet('id', $id); - - if (!$user) { - common_log(LOG_WARNING, 'No such user: ' . $id); - continue; - } - - if ($user->inboxed) { - common_log(LOG_WARNING, 'Already inboxed: ' . $id); - continue; - } - - common_log(LOG_INFO, 'Updating inbox for user ' . $user->id); - - $user->query('BEGIN'); - - $old_inbox = new Notice_inbox(); - $old_inbox->user_id = $user->id; - - $result = $old_inbox->delete(); - - if (is_null($result) || $result === false) { - common_log_db_error($old_inbox, 'DELETE', __FILE__); - continue; - } - - $old_inbox->free(); - - $inbox = new Notice_inbox(); - - $result = $inbox->query('INSERT INTO notice_inbox (user_id, notice_id, created) ' . - 'SELECT ' . $user->id . ', notice.id, notice.created ' . - 'FROM subscription JOIN notice ON subscription.subscribed = notice.profile_id ' . - 'WHERE subscription.subscriber = ' . $user->id . ' ' . - 'AND notice.created >= subscription.created ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - 'WHERE user_id = ' . $user->id . ' ' . - 'AND notice_id = notice.id) ' . - 'ORDER BY notice.created DESC ' . - 'LIMIT 0, 1000'); - - if (is_null($result) || $result === false) { - common_log_db_error($inbox, 'INSERT', __FILE__); - continue; - } - - $orig = clone($user); - $user->inboxed = 1; - $result = $user->update($orig); - - if (!$result) { - common_log_db_error($user, 'UPDATE', __FILE__); - continue; - } - - $user->query('COMMIT'); - - $inbox->free(); - unset($inbox); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last')); - } -} diff --git a/scripts/triminboxes.php b/scripts/triminboxes.php deleted file mode 100644 index da09817e5b..0000000000 --- a/scripts/triminboxes.php +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'u::'; -$longoptions = array('start-user-id::'); - -$helptext = << - --start-user-id= User ID to start after. Default is all. - -END_OF_TRIM_HELP; - -require_once INSTALLDIR.'/scripts/commandline.inc'; - -$id = null; - -if (have_option('u')) { - $id = get_option_value('u'); -} else if (have_option('--start-user-id')) { - $id = get_option_value('--start-user-id'); -} else { - $id = null; -} - -$user = new User(); - -if (!empty($id)) { - $user->whereAdd('id > ' . $id); -} - -$cnt = $user->find(); - -while ($user->fetch()) { - Notice_inbox::gc($user->id); -} From ed5e91d60da8c7e2796b8dc42243b39ded009516 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 30 Dec 2009 09:06:07 -1000 Subject: [PATCH 05/57] Use inbox instead of notice_inbox --- classes/Inbox.php | 53 ++++++++++++++++++++++++++++++ classes/Notice.php | 20 ++---------- classes/User.php | 81 ++++++++++------------------------------------ 3 files changed, 72 insertions(+), 82 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index 35f532c061..de48d73818 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -31,6 +31,8 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Inbox extends Memcached_DataObject { + const BOXCAR = 128; + ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -48,4 +50,55 @@ class Inbox extends Memcached_DataObject { return array(false, false, false); } + + static function insertNotice($user_id, $notice_id) + { + $inbox = new Inbox(); + + $inbox->query(sprintf('UPDATE inbox '. + 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'substr(notice_ids, 1, 4092)) '. + 'WHERE user_id = %d', + $notice_id, $user_id)); + } + + static function bulkInsert($notice_id, $user_ids) + { + $cnt = count($user_ids); + + for ($off = 0; $off < $cnt; $off += self::BOXCAR) { + + $boxcar = array_slice($user_ids, $off, self::BOXCAR); + + if (empty($boxcar)) { // jump in, hobo! + break; + } + + $inbox = new Inbox(); + + $inbox->query(sprintf('UPDATE inbox '. + 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'substr(notice_ids, 1, 4092)) '. + 'WHERE user_id in (%s)', + $notice_id, implode(',', $boxcar))); + + $inbox->free(); + } + } + + function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + { + $inbox = Inbox::staticGet('user_id', $user_id); + + if (empty($inbox)) { + return array(); + } + + $ids = unpack('L*', $inbox->notice_ids); + + // XXX: handle since_id + // XXX: handle max_id + + $ids = array_slice($ids, $offset, $limit); + } } diff --git a/classes/Notice.php b/classes/Notice.php index fe3f3c0170..4c6d256d3e 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -125,8 +125,7 @@ class Notice extends Memcached_DataObject 'Fave', 'Notice_tag', 'Group_inbox', - 'Queue_item', - 'Notice_inbox'); + 'Queue_item'); foreach ($related as $cls) { $inst = new $cls(); @@ -504,17 +503,6 @@ class Notice extends Memcached_DataObject unset($original); } - $ni = new Notice_inbox(); - - $ni->notice_id = $this->id; - - if ($ni->find()) { - while ($ni->fetch()) { - $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); - $cache->delete($tmk); - } - } - $ni->free(); unset($ni); } @@ -844,10 +832,6 @@ class Notice extends Memcached_DataObject function addToInboxes() { - // XXX: loads constants - - $inbox = new Notice_inbox(); - $users = $this->getSubscribedUsers(); // FIXME: kind of ignoring 'transitional'... @@ -887,7 +871,7 @@ class Notice extends Memcached_DataObject } } - Notice_inbox::bulkInsert($this->id, $this->created, $ni); + Inbox::bulkInsert($this->id, array_keys($ni)); return; } diff --git a/classes/User.php b/classes/User.php index 34151778c5..773723da40 100644 --- a/classes/User.php +++ b/classes/User.php @@ -291,6 +291,19 @@ class User extends Memcached_DataObject return false; } + // Everyone gets an inbox + + $inbox = new Inbox(); + + $inbox->user_id = $user->id; + + $result = $inbox->insert(); + + if (!$result) { + common_log_db_error($inbox, 'INSERT', __FILE__); + return false; + } + // Everyone is subscribed to themself $subscription = new Subscription(); @@ -482,89 +495,30 @@ class User extends Memcached_DataObject function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); - + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); return Notice::getStreamByIds($ids); } function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); - + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); return Notice::getStreamByIds($ids); } function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice::stream(array($this, '_friendsTimelineDirect'), - array(false), - 'user:friends_timeline:'.$this->id, - $offset, $limit, $since_id, $before_id, $since); + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); return Notice::getStreamByIds($ids); } function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice::stream(array($this, '_friendsTimelineDirect'), - array(true), - 'user:friends_timeline_own:'.$this->id, - $offset, $limit, $since_id, $before_id, $since); + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); return Notice::getStreamByIds($ids); } - function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since) - { - $qry = - 'SELECT notice.id AS id ' . - 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . - 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . - 'AND notice.repeat_of IS NULL '; - - if (!$own) { - // XXX: autoload notice inbox for constant - $inbox = new Notice_inbox(); - - $qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' '; - } - - if ($since_id != 0) { - $qry .= 'AND notice.id > ' . $since_id . ' '; - } - - if ($max_id != 0) { - $qry .= 'AND notice.id <= ' . $max_id . ' '; - } - - if (!is_null($since)) { - $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; - } - - // NOTE: we sort by fave time, not by notice time! - - $qry .= 'ORDER BY notice_id DESC '; - - if (!is_null($offset)) { - $qry .= "LIMIT $limit OFFSET $offset"; - } - - $ids = array(); - - $notice = new Notice(); - - $notice->query($qry); - - while ($notice->fetch()) { - $ids[] = $notice->id; - } - - $notice->free(); - $notice = NULL; - - return $ids; - } - function blowFavesCache() { $cache = common_memcache(); @@ -777,7 +731,6 @@ class User extends Memcached_DataObject 'Remember_me', 'Foreign_link', 'Invitation', - 'Notice_inbox', ); Event::handle('UserDeleteRelated', array($this, &$related)); From ac7a1387ba35afd725cadac436c26824c9c9c39c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 31 Dec 2009 09:09:07 -1000 Subject: [PATCH 06/57] some formatting changes to make inblobs work --- classes/Inbox.php | 8 +++++--- classes/User.php | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index de48d73818..610b5fceb2 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -56,7 +56,7 @@ class Inbox extends Memcached_DataObject $inbox = new Inbox(); $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'set notice_ids = concat(cast(0x%08x as binary(4)), '. 'substr(notice_ids, 1, 4092)) '. 'WHERE user_id = %d', $notice_id, $user_id)); @@ -77,7 +77,7 @@ class Inbox extends Memcached_DataObject $inbox = new Inbox(); $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'set notice_ids = concat(cast(0x%08x as binary(4)), '. 'substr(notice_ids, 1, 4092)) '. 'WHERE user_id in (%s)', $notice_id, implode(',', $boxcar))); @@ -94,11 +94,13 @@ class Inbox extends Memcached_DataObject return array(); } - $ids = unpack('L*', $inbox->notice_ids); + $ids = unpack('N*', $inbox->notice_ids); // XXX: handle since_id // XXX: handle max_id $ids = array_slice($ids, $offset, $limit); + + return $ids; } } diff --git a/classes/User.php b/classes/User.php index 773723da40..bde3f71b92 100644 --- a/classes/User.php +++ b/classes/User.php @@ -296,6 +296,7 @@ class User extends Memcached_DataObject $inbox = new Inbox(); $inbox->user_id = $user->id; + $inbox->notice_ids = ''; $result = $inbox->insert(); From cde1f998386a59f2eba65bff37a177d9e7a428f9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 2 Jan 2010 22:46:13 -1000 Subject: [PATCH 07/57] Disk cache plugin --- plugins/DiskCachePlugin.php | 155 ++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 plugins/DiskCachePlugin.php diff --git a/plugins/DiskCachePlugin.php b/plugins/DiskCachePlugin.php new file mode 100644 index 0000000000..86aedd19cc --- /dev/null +++ b/plugins/DiskCachePlugin.php @@ -0,0 +1,155 @@ +. + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @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')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * A plugin to cache data on local disk + * + * @category Cache + * @package StatusNet + * @author Evan Prodromou + * @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/ + */ + +class DiskCachePlugin extends Plugin +{ + var $root = '/tmp'; + + function keyToFilename($key) + { + return $this->root . '/' . str_replace(':', '/', $key); + } + + /** + * Get a value associated with a key + * + * The value should have been set previously. + * + * @param string &$key in; Lookup key + * @param mixed &$value out; value associated with key + * + * @return boolean hook success + */ + + function onStartCacheGet(&$key, &$value) + { + $filename = $this->keyToFilename($key); + if (file_exists($filename)) { + $this->log(LOG_INFO, "Cache hit on key '$key'"); + $data = file_get_contents($filename); + $value = unserialize($data); + } else { + $this->log(LOG_INFO, "Cache miss on key '$key'"); + } + + Event::handle('EndCacheGet', array($key, &$value)); + return false; + } + + /** + * Associate a value with a key + * + * @param string &$key in; Key to use for lookups + * @param mixed &$value in; Value to associate + * @param integer &$flag in; Flag (passed through to Memcache) + * @param integer &$expiry in; Expiry (passed through to Memcache) + * @param boolean &$success out; Whether the set was successful + * + * @return boolean hook success + */ + + function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success) + { + $this->log(LOG_INFO, "Setting value for key '$key'"); + + $filename = $this->keyToFilename($key); + $parent = dirname($filename); + + $sofar = ''; + + foreach (explode('/', $parent) as $part) { + if (empty($part)) { + continue; + } + $sofar .= '/' . $part; + if (!is_dir($sofar)) { + $this->debug("Creating new directory '$sofar'"); + $success = mkdir($sofar, 0750); + if (!$success) { + $this->log(LOG_ERR, "Can't create directory '$sofar'"); + return false; + } + } + } + + if (is_dir($filename)) { + $success = false; + return false; + } + + file_put_contents($filename, serialize($value)); + + Event::handle('EndCacheSet', array($key, $value, $flag, + $expiry)); + + return false; + } + + /** + * Delete a value associated with a key + * + * @param string &$key in; Key to lookup + * @param boolean &$success out; whether it worked + * + * @return boolean hook success + */ + + function onStartCacheDelete(&$key, &$success) + { + $this->log(LOG_INFO, "Deleting value for key '$key'"); + + $filename = $this->keyToFilename($key); + + if (file_exists($filename) && !is_dir($filename)) { + unlink($filename); + } + + Event::handle('EndCacheDelete', array($key)); + return false; + } +} + From f13cad656e1f53d8f801a4787bd8b28df427c6c2 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 4 Jan 2010 22:48:48 -1000 Subject: [PATCH 08/57] remove logging stuff from DiskCache --- plugins/DiskCachePlugin.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/DiskCachePlugin.php b/plugins/DiskCachePlugin.php index 86aedd19cc..2b788decb9 100644 --- a/plugins/DiskCachePlugin.php +++ b/plugins/DiskCachePlugin.php @@ -69,11 +69,8 @@ class DiskCachePlugin extends Plugin { $filename = $this->keyToFilename($key); if (file_exists($filename)) { - $this->log(LOG_INFO, "Cache hit on key '$key'"); $data = file_get_contents($filename); $value = unserialize($data); - } else { - $this->log(LOG_INFO, "Cache miss on key '$key'"); } Event::handle('EndCacheGet', array($key, &$value)); @@ -94,8 +91,6 @@ class DiskCachePlugin extends Plugin function onStartCacheSet(&$key, &$value, &$flag, &$expiry, &$success) { - $this->log(LOG_INFO, "Setting value for key '$key'"); - $filename = $this->keyToFilename($key); $parent = dirname($filename); @@ -140,8 +135,6 @@ class DiskCachePlugin extends Plugin function onStartCacheDelete(&$key, &$success) { - $this->log(LOG_INFO, "Deleting value for key '$key'"); - $filename = $this->keyToFilename($key); if (file_exists($filename) && !is_dir($filename)) { From 922db17259376d249374625324a77eee194f5ac8 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 21:54:08 -0800 Subject: [PATCH 09/57] add an inbox blob table --- db/statusnet.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/db/statusnet.sql b/db/statusnet.sql index 94b03df639..cb33ccf33e 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -596,3 +596,11 @@ create table user_location_prefs ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; +create table inbox ( + + user_id integer not null comment 'user receiving the notice' references user (id), + notice_ids blob comment 'packed list of notice ids', + + constraint primary key (user_id) + +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; From 8b2f6c01fb3e15007ec7466f6a8f53e3b13ca41b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 22:02:46 -0800 Subject: [PATCH 10/57] add inbox data class --- classes/Inbox.php | 51 +++++++++++++++++++++++++++++++++++++++++++ classes/statusnet.ini | 7 ++++++ 2 files changed, 58 insertions(+) create mode 100755 classes/Inbox.php diff --git a/classes/Inbox.php b/classes/Inbox.php new file mode 100755 index 0000000000..35f532c061 --- /dev/null +++ b/classes/Inbox.php @@ -0,0 +1,51 @@ +. + * + * @category Data + * @package StatusNet + * @author Evan Prodromou + * @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/ + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class Inbox extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'inbox'; // table name + public $user_id; // int(4) primary_key not_null + public $notice_ids; // blob + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Inbox',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 0db2c5d6e3..73727a6d6a 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -241,6 +241,13 @@ address = 130 address_type = 130 created = 142 +[inbox] +user_id = 129 +notice_ids = 66 + +[inbox__keys] +user_id = K + [invitation__keys] code = K From 475bf6010f18a17042d9171998c82e787c5abf74 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 29 Dec 2009 22:03:06 -0800 Subject: [PATCH 11/57] flip exe bit --- classes/Inbox.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 classes/Inbox.php diff --git a/classes/Inbox.php b/classes/Inbox.php old mode 100755 new mode 100644 From 7ec27b657e6b7ad9416e531cf70fffcf48614708 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 30 Dec 2009 09:03:06 -1000 Subject: [PATCH 12/57] Replace Notice_inbox with Inbox --- classes/Notice_inbox.php | 193 --------------------------------------- scripts/inbox_users.php | 107 ---------------------- scripts/triminboxes.php | 76 --------------- 3 files changed, 376 deletions(-) delete mode 100644 classes/Notice_inbox.php delete mode 100755 scripts/inbox_users.php delete mode 100644 scripts/triminboxes.php diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php deleted file mode 100644 index e350e6e2f8..0000000000 --- a/classes/Notice_inbox.php +++ /dev/null @@ -1,193 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; - -// We keep 5 pages of inbox notices in memcache, +1 for pagination check - -define('INBOX_CACHE_WINDOW', 101); -define('NOTICE_INBOX_GC_BOXCAR', 128); -define('NOTICE_INBOX_GC_MAX', 12800); -define('NOTICE_INBOX_LIMIT', 1000); -define('NOTICE_INBOX_SOFT_LIMIT', 1000); - -define('NOTICE_INBOX_SOURCE_SUB', 1); -define('NOTICE_INBOX_SOURCE_GROUP', 2); -define('NOTICE_INBOX_SOURCE_REPLY', 3); -define('NOTICE_INBOX_SOURCE_FORWARD', 4); -define('NOTICE_INBOX_SOURCE_GATEWAY', -1); - -class Notice_inbox extends Memcached_DataObject -{ - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'notice_inbox'; // table name - public $user_id; // int(4) primary_key not_null - public $notice_id; // int(4) primary_key not_null - public $created; // datetime() not_null - public $source; // tinyint(1) default_1 - - /* Static get */ - function staticGet($k,$v=null) - { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); } - - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - - function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) - { - return Notice::stream(array('Notice_inbox', '_streamDirect'), - array($user_id, $own), - ($own) ? 'notice_inbox:by_user:'.$user_id : - 'notice_inbox:by_user_own:'.$user_id, - $offset, $limit, $since_id, $max_id, $since); - } - - function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) - { - $inbox = new Notice_inbox(); - - $inbox->user_id = $user_id; - - if (!$own) { - $inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY); - } - - if ($since_id != 0) { - $inbox->whereAdd('notice_id > ' . $since_id); - } - - if ($max_id != 0) { - $inbox->whereAdd('notice_id <= ' . $max_id); - } - - if (!is_null($since)) { - $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - - $inbox->orderBy('created DESC'); - - if (!is_null($offset)) { - $inbox->limit($offset, $limit); - } - - $ids = array(); - - if ($inbox->find()) { - while ($inbox->fetch()) { - $ids[] = $inbox->notice_id; - } - } - - return $ids; - } - - function pkeyGet($kv) - { - return Memcached_DataObject::pkeyGet('Notice_inbox', $kv); - } - - /** - * Trim inbox for a given user to latest NOTICE_INBOX_LIMIT items - * (up to NOTICE_INBOX_GC_MAX will be deleted). - * - * @param int $user_id - * @return int count of notices dropped from the inbox, if any - */ - static function gc($user_id) - { - $entry = new Notice_inbox(); - $entry->user_id = $user_id; - $entry->orderBy('created DESC'); - $entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX); - - $total = $entry->find(); - - if ($total > 0) { - $notices = array(); - $cnt = 0; - while ($entry->fetch()) { - $notices[] = $entry->notice_id; - $cnt++; - if ($cnt >= NOTICE_INBOX_GC_BOXCAR) { - self::deleteMatching($user_id, $notices); - $notices = array(); - $cnt = 0; - } - } - - if ($cnt > 0) { - self::deleteMatching($user_id, $notices); - $notices = array(); - } - } - - return $total; - } - - static function deleteMatching($user_id, $notices) - { - $entry = new Notice_inbox(); - return $entry->query('DELETE FROM notice_inbox '. - 'WHERE user_id = ' . $user_id . ' ' . - 'AND notice_id in ('.implode(',', $notices).')'); - } - - static function bulkInsert($notice_id, $created, $ni) - { - $cnt = 0; - - $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES '; - $qry = $qryhdr; - - foreach ($ni as $id => $source) { - if ($cnt > 0) { - $qry .= ', '; - } - $qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') "; - $cnt++; - if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { - // FIXME: Causes lag in replicated servers - // Notice_inbox::gc($id); - } - if ($cnt >= MAX_BOXCARS) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - $qry = $qryhdr; - $cnt = 0; - } - } - - if ($cnt > 0) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - } - - return; - } -} diff --git a/scripts/inbox_users.php b/scripts/inbox_users.php deleted file mode 100755 index 32adcea213..0000000000 --- a/scripts/inbox_users.php +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env php -. - */ - -# Abort if called from a web server - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$helptext = << - -Update users to use inbox table. Listed in an ID file, default 'ids.txt'. - -ENDOFHELP; - -require_once INSTALLDIR.'/scripts/commandline.inc'; - -$id_file = (count($args) > 1) ? $args[0] : 'ids.txt'; - -common_log(LOG_INFO, 'Updating user inboxes.'); - -$ids = file($id_file); - -foreach ($ids as $id) { - - $user = User::staticGet('id', $id); - - if (!$user) { - common_log(LOG_WARNING, 'No such user: ' . $id); - continue; - } - - if ($user->inboxed) { - common_log(LOG_WARNING, 'Already inboxed: ' . $id); - continue; - } - - common_log(LOG_INFO, 'Updating inbox for user ' . $user->id); - - $user->query('BEGIN'); - - $old_inbox = new Notice_inbox(); - $old_inbox->user_id = $user->id; - - $result = $old_inbox->delete(); - - if (is_null($result) || $result === false) { - common_log_db_error($old_inbox, 'DELETE', __FILE__); - continue; - } - - $old_inbox->free(); - - $inbox = new Notice_inbox(); - - $result = $inbox->query('INSERT INTO notice_inbox (user_id, notice_id, created) ' . - 'SELECT ' . $user->id . ', notice.id, notice.created ' . - 'FROM subscription JOIN notice ON subscription.subscribed = notice.profile_id ' . - 'WHERE subscription.subscriber = ' . $user->id . ' ' . - 'AND notice.created >= subscription.created ' . - 'AND NOT EXISTS (SELECT user_id, notice_id ' . - 'FROM notice_inbox ' . - 'WHERE user_id = ' . $user->id . ' ' . - 'AND notice_id = notice.id) ' . - 'ORDER BY notice.created DESC ' . - 'LIMIT 0, 1000'); - - if (is_null($result) || $result === false) { - common_log_db_error($inbox, 'INSERT', __FILE__); - continue; - } - - $orig = clone($user); - $user->inboxed = 1; - $result = $user->update($orig); - - if (!$result) { - common_log_db_error($user, 'UPDATE', __FILE__); - continue; - } - - $user->query('COMMIT'); - - $inbox->free(); - unset($inbox); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last')); - } -} diff --git a/scripts/triminboxes.php b/scripts/triminboxes.php deleted file mode 100644 index ea47513051..0000000000 --- a/scripts/triminboxes.php +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'u::'; -$longoptions = array('start-user-id=', 'sleep-time='); - -$helptext = << - --start-user-id= User ID to start after. Default is all. - --sleep-time= Amount of time to wait (in seconds) between trims. Default is zero. - -END_OF_TRIM_HELP; - -require_once INSTALLDIR.'/scripts/commandline.inc'; - -$id = null; -$sleep_time = 0; - -if (have_option('u')) { - $id = get_option_value('u'); -} else if (have_option('--start-user-id')) { - $id = get_option_value('--start-user-id'); -} else { - $id = null; -} - -if (have_option('--sleep-time')) { - $sleep_time = intval(get_option_value('--sleep-time')); -} - -$quiet = have_option('q') || have_option('--quiet'); - -$user = new User(); - -if (!empty($id)) { - $user->whereAdd('id > ' . $id); -} - -$cnt = $user->find(); - -while ($user->fetch()) { - if (!$quiet) { - print "Trimming inbox for user $user->id"; - } - $count = Notice_inbox::gc($user->id); - if ($count) { - if (!$quiet) { - print ": $count trimmed..."; - } - sleep($sleep_time); - } - if (!$quiet) { - print "\n"; - } -} From f2a403589cc8858ec3d3ce70f09709f36277c348 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 30 Dec 2009 09:06:07 -1000 Subject: [PATCH 13/57] Use inbox instead of notice_inbox --- classes/Inbox.php | 53 ++++++++++++++++++++++++++++++ classes/Notice.php | 20 ++---------- classes/User.php | 81 ++++++++++------------------------------------ 3 files changed, 72 insertions(+), 82 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index 35f532c061..de48d73818 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -31,6 +31,8 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Inbox extends Memcached_DataObject { + const BOXCAR = 128; + ###START_AUTOCODE /* the code below is auto generated do not remove the above tag */ @@ -48,4 +50,55 @@ class Inbox extends Memcached_DataObject { return array(false, false, false); } + + static function insertNotice($user_id, $notice_id) + { + $inbox = new Inbox(); + + $inbox->query(sprintf('UPDATE inbox '. + 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'substr(notice_ids, 1, 4092)) '. + 'WHERE user_id = %d', + $notice_id, $user_id)); + } + + static function bulkInsert($notice_id, $user_ids) + { + $cnt = count($user_ids); + + for ($off = 0; $off < $cnt; $off += self::BOXCAR) { + + $boxcar = array_slice($user_ids, $off, self::BOXCAR); + + if (empty($boxcar)) { // jump in, hobo! + break; + } + + $inbox = new Inbox(); + + $inbox->query(sprintf('UPDATE inbox '. + 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'substr(notice_ids, 1, 4092)) '. + 'WHERE user_id in (%s)', + $notice_id, implode(',', $boxcar))); + + $inbox->free(); + } + } + + function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + { + $inbox = Inbox::staticGet('user_id', $user_id); + + if (empty($inbox)) { + return array(); + } + + $ids = unpack('L*', $inbox->notice_ids); + + // XXX: handle since_id + // XXX: handle max_id + + $ids = array_slice($ids, $offset, $limit); + } } diff --git a/classes/Notice.php b/classes/Notice.php index 9bda478271..8783912e89 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -125,8 +125,7 @@ class Notice extends Memcached_DataObject 'Fave', 'Notice_tag', 'Group_inbox', - 'Queue_item', - 'Notice_inbox'); + 'Queue_item'); foreach ($related as $cls) { $inst = new $cls(); @@ -504,17 +503,6 @@ class Notice extends Memcached_DataObject unset($original); } - $ni = new Notice_inbox(); - - $ni->notice_id = $this->id; - - if ($ni->find()) { - while ($ni->fetch()) { - $tmk = common_cache_key('user:repeated_to_me:'.$ni->user_id); - $cache->delete($tmk); - } - } - $ni->free(); unset($ni); } @@ -844,10 +832,6 @@ class Notice extends Memcached_DataObject function addToInboxes() { - // XXX: loads constants - - $inbox = new Notice_inbox(); - $users = $this->getSubscribedUsers(); // FIXME: kind of ignoring 'transitional'... @@ -887,7 +871,7 @@ class Notice extends Memcached_DataObject } } - Notice_inbox::bulkInsert($this->id, $this->created, $ni); + Inbox::bulkInsert($this->id, array_keys($ni)); return; } diff --git a/classes/User.php b/classes/User.php index 34151778c5..773723da40 100644 --- a/classes/User.php +++ b/classes/User.php @@ -291,6 +291,19 @@ class User extends Memcached_DataObject return false; } + // Everyone gets an inbox + + $inbox = new Inbox(); + + $inbox->user_id = $user->id; + + $result = $inbox->insert(); + + if (!$result) { + common_log_db_error($inbox, 'INSERT', __FILE__); + return false; + } + // Everyone is subscribed to themself $subscription = new Subscription(); @@ -482,89 +495,30 @@ class User extends Memcached_DataObject function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); - + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); return Notice::getStreamByIds($ids); } function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); - + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); return Notice::getStreamByIds($ids); } function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice::stream(array($this, '_friendsTimelineDirect'), - array(false), - 'user:friends_timeline:'.$this->id, - $offset, $limit, $since_id, $before_id, $since); + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false); return Notice::getStreamByIds($ids); } function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) { - $ids = Notice::stream(array($this, '_friendsTimelineDirect'), - array(true), - 'user:friends_timeline_own:'.$this->id, - $offset, $limit, $since_id, $before_id, $since); + $ids = Inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true); return Notice::getStreamByIds($ids); } - function _friendsTimelineDirect($own, $offset, $limit, $since_id, $max_id, $since) - { - $qry = - 'SELECT notice.id AS id ' . - 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . - 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . - 'AND notice.repeat_of IS NULL '; - - if (!$own) { - // XXX: autoload notice inbox for constant - $inbox = new Notice_inbox(); - - $qry .= 'AND notice_inbox.source != ' . NOTICE_INBOX_SOURCE_GATEWAY . ' '; - } - - if ($since_id != 0) { - $qry .= 'AND notice.id > ' . $since_id . ' '; - } - - if ($max_id != 0) { - $qry .= 'AND notice.id <= ' . $max_id . ' '; - } - - if (!is_null($since)) { - $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; - } - - // NOTE: we sort by fave time, not by notice time! - - $qry .= 'ORDER BY notice_id DESC '; - - if (!is_null($offset)) { - $qry .= "LIMIT $limit OFFSET $offset"; - } - - $ids = array(); - - $notice = new Notice(); - - $notice->query($qry); - - while ($notice->fetch()) { - $ids[] = $notice->id; - } - - $notice->free(); - $notice = NULL; - - return $ids; - } - function blowFavesCache() { $cache = common_memcache(); @@ -777,7 +731,6 @@ class User extends Memcached_DataObject 'Remember_me', 'Foreign_link', 'Invitation', - 'Notice_inbox', ); Event::handle('UserDeleteRelated', array($this, &$related)); From 1e7ec69190e089f89b57181d561e2df2c8bdd3b5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 31 Dec 2009 09:09:07 -1000 Subject: [PATCH 14/57] some formatting changes to make inblobs work --- classes/Inbox.php | 8 +++++--- classes/User.php | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index de48d73818..610b5fceb2 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -56,7 +56,7 @@ class Inbox extends Memcached_DataObject $inbox = new Inbox(); $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'set notice_ids = concat(cast(0x%08x as binary(4)), '. 'substr(notice_ids, 1, 4092)) '. 'WHERE user_id = %d', $notice_id, $user_id)); @@ -77,7 +77,7 @@ class Inbox extends Memcached_DataObject $inbox = new Inbox(); $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(%08x as binary(4)), '. + 'set notice_ids = concat(cast(0x%08x as binary(4)), '. 'substr(notice_ids, 1, 4092)) '. 'WHERE user_id in (%s)', $notice_id, implode(',', $boxcar))); @@ -94,11 +94,13 @@ class Inbox extends Memcached_DataObject return array(); } - $ids = unpack('L*', $inbox->notice_ids); + $ids = unpack('N*', $inbox->notice_ids); // XXX: handle since_id // XXX: handle max_id $ids = array_slice($ids, $offset, $limit); + + return $ids; } } diff --git a/classes/User.php b/classes/User.php index 773723da40..bde3f71b92 100644 --- a/classes/User.php +++ b/classes/User.php @@ -296,6 +296,7 @@ class User extends Memcached_DataObject $inbox = new Inbox(); $inbox->user_id = $user->id; + $inbox->notice_ids = ''; $result = $inbox->insert(); From 72934e9f50ee8970372b39b3ff27dc4ded30e0c5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 09:31:57 -0800 Subject: [PATCH 15/57] Revert "Replace Notice_inbox with Inbox" We use Notice_inbox to transition to Inbox. This reverts commit 7640d3f07bad0710d69575efc7ceda115f24a60a. --- classes/Notice_inbox.php | 184 +++++++++++++++++++++++++++++++++++++++ scripts/inbox_users.php | 107 +++++++++++++++++++++++ scripts/triminboxes.php | 56 ++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 classes/Notice_inbox.php create mode 100755 scripts/inbox_users.php create mode 100644 scripts/triminboxes.php diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php new file mode 100644 index 0000000000..b39006542c --- /dev/null +++ b/classes/Notice_inbox.php @@ -0,0 +1,184 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +// We keep 5 pages of inbox notices in memcache, +1 for pagination check + +define('INBOX_CACHE_WINDOW', 101); +define('NOTICE_INBOX_GC_BOXCAR', 128); +define('NOTICE_INBOX_GC_MAX', 12800); +define('NOTICE_INBOX_LIMIT', 1000); +define('NOTICE_INBOX_SOFT_LIMIT', 1000); + +define('NOTICE_INBOX_SOURCE_SUB', 1); +define('NOTICE_INBOX_SOURCE_GROUP', 2); +define('NOTICE_INBOX_SOURCE_REPLY', 3); +define('NOTICE_INBOX_SOURCE_FORWARD', 4); +define('NOTICE_INBOX_SOURCE_GATEWAY', -1); + +class Notice_inbox extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'notice_inbox'; // table name + public $user_id; // int(4) primary_key not_null + public $notice_id; // int(4) primary_key not_null + public $created; // datetime() not_null + public $source; // tinyint(1) default_1 + + /* Static get */ + function staticGet($k,$v=null) + { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) + { + return Notice::stream(array('Notice_inbox', '_streamDirect'), + array($user_id, $own), + ($own) ? 'notice_inbox:by_user:'.$user_id : + 'notice_inbox:by_user_own:'.$user_id, + $offset, $limit, $since_id, $max_id, $since); + } + + function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) + { + $inbox = new Notice_inbox(); + + $inbox->user_id = $user_id; + + if (!$own) { + $inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY); + } + + if ($since_id != 0) { + $inbox->whereAdd('notice_id > ' . $since_id); + } + + if ($max_id != 0) { + $inbox->whereAdd('notice_id <= ' . $max_id); + } + + if (!is_null($since)) { + $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); + } + + $inbox->orderBy('created DESC'); + + if (!is_null($offset)) { + $inbox->limit($offset, $limit); + } + + $ids = array(); + + if ($inbox->find()) { + while ($inbox->fetch()) { + $ids[] = $inbox->notice_id; + } + } + + return $ids; + } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Notice_inbox', $kv); + } + + static function gc($user_id) + { + $entry = new Notice_inbox(); + $entry->user_id = $user_id; + $entry->orderBy('created DESC'); + $entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX); + + $total = $entry->find(); + + if ($total > 0) { + $notices = array(); + $cnt = 0; + while ($entry->fetch()) { + $notices[] = $entry->notice_id; + $cnt++; + if ($cnt >= NOTICE_INBOX_GC_BOXCAR) { + self::deleteMatching($user_id, $notices); + $notices = array(); + $cnt = 0; + } + } + + if ($cnt > 0) { + self::deleteMatching($user_id, $notices); + $notices = array(); + } + } + } + + static function deleteMatching($user_id, $notices) + { + $entry = new Notice_inbox(); + return $entry->query('DELETE FROM notice_inbox '. + 'WHERE user_id = ' . $user_id . ' ' . + 'AND notice_id in ('.implode(',', $notices).')'); + } + + static function bulkInsert($notice_id, $created, $ni) + { + $cnt = 0; + + $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES '; + $qry = $qryhdr; + + foreach ($ni as $id => $source) { + if ($cnt > 0) { + $qry .= ', '; + } + $qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') "; + $cnt++; + if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { + // FIXME: Causes lag in replicated servers + // Notice_inbox::gc($id); + } + if ($cnt >= MAX_BOXCARS) { + $inbox = new Notice_inbox(); + $result = $inbox->query($qry); + if (PEAR::isError($result)) { + common_log_db_error($inbox, $qry); + } + $qry = $qryhdr; + $cnt = 0; + } + } + + if ($cnt > 0) { + $inbox = new Notice_inbox(); + $result = $inbox->query($qry); + if (PEAR::isError($result)) { + common_log_db_error($inbox, $qry); + } + } + + return; + } +} diff --git a/scripts/inbox_users.php b/scripts/inbox_users.php new file mode 100755 index 0000000000..32adcea213 --- /dev/null +++ b/scripts/inbox_users.php @@ -0,0 +1,107 @@ +#!/usr/bin/env php +. + */ + +# Abort if called from a web server + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$helptext = << + +Update users to use inbox table. Listed in an ID file, default 'ids.txt'. + +ENDOFHELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +$id_file = (count($args) > 1) ? $args[0] : 'ids.txt'; + +common_log(LOG_INFO, 'Updating user inboxes.'); + +$ids = file($id_file); + +foreach ($ids as $id) { + + $user = User::staticGet('id', $id); + + if (!$user) { + common_log(LOG_WARNING, 'No such user: ' . $id); + continue; + } + + if ($user->inboxed) { + common_log(LOG_WARNING, 'Already inboxed: ' . $id); + continue; + } + + common_log(LOG_INFO, 'Updating inbox for user ' . $user->id); + + $user->query('BEGIN'); + + $old_inbox = new Notice_inbox(); + $old_inbox->user_id = $user->id; + + $result = $old_inbox->delete(); + + if (is_null($result) || $result === false) { + common_log_db_error($old_inbox, 'DELETE', __FILE__); + continue; + } + + $old_inbox->free(); + + $inbox = new Notice_inbox(); + + $result = $inbox->query('INSERT INTO notice_inbox (user_id, notice_id, created) ' . + 'SELECT ' . $user->id . ', notice.id, notice.created ' . + 'FROM subscription JOIN notice ON subscription.subscribed = notice.profile_id ' . + 'WHERE subscription.subscriber = ' . $user->id . ' ' . + 'AND notice.created >= subscription.created ' . + 'AND NOT EXISTS (SELECT user_id, notice_id ' . + 'FROM notice_inbox ' . + 'WHERE user_id = ' . $user->id . ' ' . + 'AND notice_id = notice.id) ' . + 'ORDER BY notice.created DESC ' . + 'LIMIT 0, 1000'); + + if (is_null($result) || $result === false) { + common_log_db_error($inbox, 'INSERT', __FILE__); + continue; + } + + $orig = clone($user); + $user->inboxed = 1; + $result = $user->update($orig); + + if (!$result) { + common_log_db_error($user, 'UPDATE', __FILE__); + continue; + } + + $user->query('COMMIT'); + + $inbox->free(); + unset($inbox); + + if ($cache) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last')); + } +} diff --git a/scripts/triminboxes.php b/scripts/triminboxes.php new file mode 100644 index 0000000000..da09817e5b --- /dev/null +++ b/scripts/triminboxes.php @@ -0,0 +1,56 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'u::'; +$longoptions = array('start-user-id::'); + +$helptext = << + --start-user-id= User ID to start after. Default is all. + +END_OF_TRIM_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +$id = null; + +if (have_option('u')) { + $id = get_option_value('u'); +} else if (have_option('--start-user-id')) { + $id = get_option_value('--start-user-id'); +} else { + $id = null; +} + +$user = new User(); + +if (!empty($id)) { + $user->whereAdd('id > ' . $id); +} + +$cnt = $user->find(); + +while ($user->fetch()) { + Notice_inbox::gc($user->id); +} From 1de0f37f0d6e4706fe9f1ce086df730ad4a05b78 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 09:59:58 -0800 Subject: [PATCH 16/57] disable all Notice_inbox functions --- classes/Notice_inbox.php | 115 +++------------------------------------ 1 file changed, 9 insertions(+), 106 deletions(-) diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index b39006542c..6c328e6854 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -1,7 +1,7 @@ . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET')) { + exit(1); +} require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; @@ -55,50 +57,12 @@ class Notice_inbox extends Memcached_DataObject function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false) { - return Notice::stream(array('Notice_inbox', '_streamDirect'), - array($user_id, $own), - ($own) ? 'notice_inbox:by_user:'.$user_id : - 'notice_inbox:by_user_own:'.$user_id, - $offset, $limit, $since_id, $max_id, $since); + throw new Exception('Notice_inbox no longer used; use Inbox'); } function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since) { - $inbox = new Notice_inbox(); - - $inbox->user_id = $user_id; - - if (!$own) { - $inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY); - } - - if ($since_id != 0) { - $inbox->whereAdd('notice_id > ' . $since_id); - } - - if ($max_id != 0) { - $inbox->whereAdd('notice_id <= ' . $max_id); - } - - if (!is_null($since)) { - $inbox->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\''); - } - - $inbox->orderBy('created DESC'); - - if (!is_null($offset)) { - $inbox->limit($offset, $limit); - } - - $ids = array(); - - if ($inbox->find()) { - while ($inbox->fetch()) { - $ids[] = $inbox->notice_id; - } - } - - return $ids; + throw new Exception('Notice_inbox no longer used; use Inbox'); } function &pkeyGet($kv) @@ -108,77 +72,16 @@ class Notice_inbox extends Memcached_DataObject static function gc($user_id) { - $entry = new Notice_inbox(); - $entry->user_id = $user_id; - $entry->orderBy('created DESC'); - $entry->limit(NOTICE_INBOX_LIMIT - 1, NOTICE_INBOX_GC_MAX); - - $total = $entry->find(); - - if ($total > 0) { - $notices = array(); - $cnt = 0; - while ($entry->fetch()) { - $notices[] = $entry->notice_id; - $cnt++; - if ($cnt >= NOTICE_INBOX_GC_BOXCAR) { - self::deleteMatching($user_id, $notices); - $notices = array(); - $cnt = 0; - } - } - - if ($cnt > 0) { - self::deleteMatching($user_id, $notices); - $notices = array(); - } - } + throw new Exception('Notice_inbox no longer used; use Inbox'); } static function deleteMatching($user_id, $notices) { - $entry = new Notice_inbox(); - return $entry->query('DELETE FROM notice_inbox '. - 'WHERE user_id = ' . $user_id . ' ' . - 'AND notice_id in ('.implode(',', $notices).')'); + throw new Exception('Notice_inbox no longer used; use Inbox'); } static function bulkInsert($notice_id, $created, $ni) { - $cnt = 0; - - $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES '; - $qry = $qryhdr; - - foreach ($ni as $id => $source) { - if ($cnt > 0) { - $qry .= ', '; - } - $qry .= '('.$id.', '.$notice_id.', '.$source.", '".$created. "') "; - $cnt++; - if (rand() % NOTICE_INBOX_SOFT_LIMIT == 0) { - // FIXME: Causes lag in replicated servers - // Notice_inbox::gc($id); - } - if ($cnt >= MAX_BOXCARS) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - $qry = $qryhdr; - $cnt = 0; - } - } - - if ($cnt > 0) { - $inbox = new Notice_inbox(); - $result = $inbox->query($qry); - if (PEAR::isError($result)) { - common_log_db_error($inbox, $qry); - } - } - - return; + throw new Exception('Notice_inbox no longer used; use Inbox'); } } From 2272438698f5a0a71d098b0de4f4e184033b1034 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 10:00:27 -0800 Subject: [PATCH 17/57] remove triminboxes.php; it's no longer used --- scripts/triminboxes.php | 56 ----------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 scripts/triminboxes.php diff --git a/scripts/triminboxes.php b/scripts/triminboxes.php deleted file mode 100644 index da09817e5b..0000000000 --- a/scripts/triminboxes.php +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'u::'; -$longoptions = array('start-user-id::'); - -$helptext = << - --start-user-id= User ID to start after. Default is all. - -END_OF_TRIM_HELP; - -require_once INSTALLDIR.'/scripts/commandline.inc'; - -$id = null; - -if (have_option('u')) { - $id = get_option_value('u'); -} else if (have_option('--start-user-id')) { - $id = get_option_value('--start-user-id'); -} else { - $id = null; -} - -$user = new User(); - -if (!empty($id)) { - $user->whereAdd('id > ' . $id); -} - -$cnt = $user->find(); - -while ($user->fetch()) { - Notice_inbox::gc($user->id); -} From 920c878221cf87c1cf74baabbf0636337550eeab Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 10:01:07 -0800 Subject: [PATCH 18/57] initialize an Inbox from Notice_inbox records --- classes/Inbox.php | 75 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index 610b5fceb2..de52e1e0c9 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -51,15 +51,73 @@ class Inbox extends Memcached_DataObject return array(false, false, false); } - static function insertNotice($user_id, $notice_id) + /** + * Create a new inbox from existing Notice_inbox stuff + */ + + static function initialize($user_id) { + $ids = array(); + + $ni = new Notice_inbox(); + + $ni->user_id = $user_id; + $ni->selectAdd(); + $ni->selectAdd('notice_id'); + $ni->orderBy('notice_id DESC'); + $ni->limit(0, 1024); + + if ($ni->find()) { + while($ni->fetch()) { + $ids[] = $ni->notice_id; + } + } + + $ni->free(); + unset($ni); + $inbox = new Inbox(); - $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(0x%08x as binary(4)), '. - 'substr(notice_ids, 1, 4092)) '. - 'WHERE user_id = %d', - $notice_id, $user_id)); + $inbox->user_id = $user_id; + $inbox->notice_ids = pack('N*', $ids); + + $result = $inbox->insert(); + + if (!$result) { + common_log_db_error($inbox, 'INSERT', __FILE__); + return null; + } + + return $inbox; + } + + static function insertNotice($user_id, $notice_id) + { + $inbox = Inbox::staticGet('user_id', $user_id); + + if (empty($inbox)) { + $inbox = Inbox::initialize($user_id); + } + + if (empty($inbox)) { + return false; + } + + $result = $inbox->query(sprintf('UPDATE inbox '. + 'set notice_ids = concat(cast(0x%08x as binary(4)), '. + 'substr(notice_ids, 1, 4092)) '. + 'WHERE user_id = %d', + $notice_id, $user_id)); + + if ($result) { + $c = $this->memcache(); + + if (!empty($c)) { + $c->delete($this->cacheKey($this->tableName(), 'user_id', $user_id)); + } + } + + return $result; } static function bulkInsert($notice_id, $user_ids) @@ -91,7 +149,10 @@ class Inbox extends Memcached_DataObject $inbox = Inbox::staticGet('user_id', $user_id); if (empty($inbox)) { - return array(); + $inbox = Inbox::initialize($user_id); + if (empty($inbox)) { + return array(); + } } $ids = unpack('N*', $inbox->notice_ids); From 5e81149e4773799b2dcf426987e6263b8841c5cc Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 10:26:50 -0800 Subject: [PATCH 19/57] create new Inbox from Notice_inbox if not exists at read/write time --- classes/Inbox.php | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/classes/Inbox.php b/classes/Inbox.php index de52e1e0c9..e14d4f4e7e 100644 --- a/classes/Inbox.php +++ b/classes/Inbox.php @@ -79,7 +79,7 @@ class Inbox extends Memcached_DataObject $inbox = new Inbox(); $inbox->user_id = $user_id; - $inbox->notice_ids = pack('N*', $ids); + $inbox->notice_ids = call_user_func_array('pack', array_merge(array('N*'), $ids)); $result = $inbox->insert(); @@ -110,10 +110,10 @@ class Inbox extends Memcached_DataObject $notice_id, $user_id)); if ($result) { - $c = $this->memcache(); + $c = self::memcache(); if (!empty($c)) { - $c->delete($this->cacheKey($this->tableName(), 'user_id', $user_id)); + $c->delete(self::cacheKey('inbox', 'user_id', $user_id)); } } @@ -122,25 +122,9 @@ class Inbox extends Memcached_DataObject static function bulkInsert($notice_id, $user_ids) { - $cnt = count($user_ids); - - for ($off = 0; $off < $cnt; $off += self::BOXCAR) { - - $boxcar = array_slice($user_ids, $off, self::BOXCAR); - - if (empty($boxcar)) { // jump in, hobo! - break; - } - - $inbox = new Inbox(); - - $inbox->query(sprintf('UPDATE inbox '. - 'set notice_ids = concat(cast(0x%08x as binary(4)), '. - 'substr(notice_ids, 1, 4092)) '. - 'WHERE user_id in (%s)', - $notice_id, implode(',', $boxcar))); - - $inbox->free(); + foreach ($user_ids as $user_id) + { + Inbox::insertNotice($user_id, $notice_id); } } From 96e51dad4b9ff2208cfef2e578e4bc85a8937680 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 13:55:54 -0800 Subject: [PATCH 20/57] whoGets() method for Notice --- classes/Notice.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 8783912e89..6284b8ca51 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -830,7 +830,7 @@ class Notice extends Memcached_DataObject return $ids; } - function addToInboxes() + function whoGets() { $users = $this->getSubscribedUsers(); @@ -871,6 +871,13 @@ class Notice extends Memcached_DataObject } } + return $ni; + } + + function addToInboxes() + { + $ni = $this->whoGets(); + Inbox::bulkInsert($this->id, array_keys($ni)); return; From 9cfea6d051372cb1c7d177b2a0eb26f773821238 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 13:56:54 -0800 Subject: [PATCH 21/57] use whoGets() for realtime plugin --- plugins/Realtime/RealtimePlugin.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 21e465b53d..89640f5beb 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -154,14 +154,11 @@ class RealtimePlugin extends Plugin // Add to inbox timelines // XXX: do a join - $inbox = new Notice_inbox(); - $inbox->notice_id = $notice->id; + $ni = $notice->whoGets(); - if ($inbox->find()) { - while ($inbox->fetch()) { - $user = User::staticGet('id', $inbox->user_id); - $paths[] = array('all', $user->nickname); - } + foreach (array_keys($ni) as $user_id) { + $user = User::staticGet('id', $user_id); + $paths[] = array('all', $user->nickname); } // Add to the replies timeline From d902de139cbce38a40525811fe117fdf17aec244 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 13:59:39 -0800 Subject: [PATCH 22/57] change PuSH plugin to use whoGets() --- plugins/PubSubHubBub/PubSubHubBubPlugin.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/PubSubHubBub/PubSubHubBubPlugin.php b/plugins/PubSubHubBub/PubSubHubBubPlugin.php index d15a869cba..d9c047de85 100644 --- a/plugins/PubSubHubBub/PubSubHubBubPlugin.php +++ b/plugins/PubSubHubBub/PubSubHubBubPlugin.php @@ -95,14 +95,16 @@ class PubSubHubBubPlugin extends Plugin } //feed of each user that subscribes to the notice's author - $notice_inbox = new Notice_inbox(); - $notice_inbox->notice_id = $notice->id; - if ($notice_inbox->find()) { - while ($notice_inbox->fetch()) { - $user = User::staticGet('id',$notice_inbox->user_id); - $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'rss')); - $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'atom')); + + $ni = $notice->whoGets(); + + foreach (array_keys($ni) as $user_id) { + $user = User::staticGet('id', $user_id); + if (empty($user)) { + continue; } + $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'rss')); + $feeds[]=common_local_url('ApiTimelineUser',array('id' => $user->nickname, 'format'=>'atom')); } //feed of user replied to From 2db840b2f51d40c12da3edcecdc8eba820ccb575 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 9 Jan 2010 14:02:01 -0800 Subject: [PATCH 23/57] use Inbox in twitterstatusfetcher --- .../TwitterBridge/daemons/twitterstatusfetcher.php | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index b4ca12be23..36732ce46a 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -268,19 +268,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon } - if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id, - 'user_id' => $flink->user_id))) { - // Add to inbox - $inbox = new Notice_inbox(); - - $inbox->user_id = $flink->user_id; - $inbox->notice_id = $notice->id; - $inbox->created = $notice->created; - $inbox->source = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source - - $inbox->insert(); - - } + Inbox::insertNotice($flink->user_id, $notice->id); $notice->blowCaches(); From 5d676352c376fd5f69d5cfee491e6243a6e8c866 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 11 Jan 2010 15:09:46 -0800 Subject: [PATCH 24/57] strip out session ID from root URL --- lib/util.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/util.php b/lib/util.php index 1237d718bd..3e52f5db16 100644 --- a/lib/util.php +++ b/lib/util.php @@ -838,7 +838,7 @@ function common_path($relative, $ssl=false) } $relative = common_inject_session($relative, $serverpart); - + return $proto.'://'.$serverpart.'/'.$pathpart.$relative; } @@ -849,7 +849,7 @@ function common_inject_session($url, $serverpart = null) if (empty($serverpart)) { $serverpart = parse_url($url, PHP_URL_HOST); } - + $currentServer = $_SERVER['HTTP_HOST']; // Are we pointing to another server (like an SSL server?) @@ -866,7 +866,7 @@ function common_inject_session($url, $serverpart = null) } } } - + return $url; } @@ -1057,7 +1057,12 @@ function common_profile_url($nickname) function common_root_url($ssl=false) { - return common_path('', $ssl); + $url = common_path('', $ssl); + $i = strpos($url, '?'); + if ($i !== false) { + $url = substr($url, 0, $i); + } + return $url; } // returns $bytes bytes of random data as a hexadecimal string From 3b007934e49db8b9950171680d0360cae7ab4fdb Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 11 Jan 2010 15:09:46 -0800 Subject: [PATCH 25/57] strip out session ID from root URL --- lib/util.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/util.php b/lib/util.php index a56a41a577..5ffc0226f3 100644 --- a/lib/util.php +++ b/lib/util.php @@ -833,7 +833,7 @@ function common_path($relative, $ssl=false) } $relative = common_inject_session($relative, $serverpart); - + return $proto.'://'.$serverpart.'/'.$pathpart.$relative; } @@ -844,7 +844,7 @@ function common_inject_session($url, $serverpart = null) if (empty($serverpart)) { $serverpart = parse_url($url, PHP_URL_HOST); } - + $currentServer = $_SERVER['HTTP_HOST']; // Are we pointing to another server (like an SSL server?) @@ -861,7 +861,7 @@ function common_inject_session($url, $serverpart = null) } } } - + return $url; } @@ -1052,7 +1052,12 @@ function common_profile_url($nickname) function common_root_url($ssl=false) { - return common_path('', $ssl); + $url = common_path('', $ssl); + $i = strpos($url, '?'); + if ($i !== false) { + $url = substr($url, 0, $i); + } + return $url; } // returns $bytes bytes of random data as a hexadecimal string From 116c5f6839adf518e2bfe27ad2899a474899a9a1 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 11 Jan 2010 15:42:17 -0800 Subject: [PATCH 26/57] dos -> unix line endings on CasAuthentication's CAS library --- plugins/CasAuthentication/extlib/CAS.php | 2942 +++++------ .../extlib/CAS/PGTStorage/pgt-db.php | 378 +- .../extlib/CAS/PGTStorage/pgt-file.php | 496 +- .../extlib/CAS/PGTStorage/pgt-main.php | 374 +- .../CasAuthentication/extlib/CAS/client.php | 4588 ++++++++--------- .../extlib/CAS/domxml-php4-php5.php | 552 +- .../extlib/CAS/languages/catalan.php | 54 +- .../extlib/CAS/languages/english.php | 52 +- .../extlib/CAS/languages/french.php | 54 +- .../extlib/CAS/languages/german.php | 52 +- .../extlib/CAS/languages/greek.php | 52 +- .../extlib/CAS/languages/languages.php | 46 +- .../extlib/CAS/languages/spanish.php | 54 +- 13 files changed, 4847 insertions(+), 4847 deletions(-) diff --git a/plugins/CasAuthentication/extlib/CAS.php b/plugins/CasAuthentication/extlib/CAS.php index 59238eb813..f5ea0b12a7 100644 --- a/plugins/CasAuthentication/extlib/CAS.php +++ b/plugins/CasAuthentication/extlib/CAS.php @@ -1,1471 +1,1471 @@ -=')) { - require_once(dirname(__FILE__).'/CAS/domxml-php4-php5.php'); -} - -/** - * @file CAS/CAS.php - * Interface class of the phpCAS library - * - * @ingroup public - */ - -// ######################################################################## -// CONSTANTS -// ######################################################################## - -// ------------------------------------------------------------------------ -// CAS VERSIONS -// ------------------------------------------------------------------------ - -/** - * phpCAS version. accessible for the user by phpCAS::getVersion(). - */ -define('PHPCAS_VERSION','1.0.1'); - -// ------------------------------------------------------------------------ -// CAS VERSIONS -// ------------------------------------------------------------------------ - /** - * @addtogroup public - * @{ - */ - -/** - * CAS version 1.0 - */ -define("CAS_VERSION_1_0",'1.0'); -/*! - * CAS version 2.0 - */ -define("CAS_VERSION_2_0",'2.0'); - -/** @} */ - /** - * @addtogroup publicPGTStorage - * @{ - */ -// ------------------------------------------------------------------------ -// FILE PGT STORAGE -// ------------------------------------------------------------------------ - /** - * Default path used when storing PGT's to file - */ -define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH",'/tmp'); -/** - * phpCAS::setPGTStorageFile()'s 2nd parameter to write plain text files - */ -define("CAS_PGT_STORAGE_FILE_FORMAT_PLAIN",'plain'); -/** - * phpCAS::setPGTStorageFile()'s 2nd parameter to write xml files - */ -define("CAS_PGT_STORAGE_FILE_FORMAT_XML",'xml'); -/** - * Default format used when storing PGT's to file - */ -define("CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT",CAS_PGT_STORAGE_FILE_FORMAT_PLAIN); -// ------------------------------------------------------------------------ -// DATABASE PGT STORAGE -// ------------------------------------------------------------------------ - /** - * default database type when storing PGT's to database - */ -define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE",'mysql'); -/** - * default host when storing PGT's to database - */ -define("CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME",'localhost'); -/** - * default port when storing PGT's to database - */ -define("CAS_PGT_STORAGE_DB_DEFAULT_PORT",''); -/** - * default database when storing PGT's to database - */ -define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE",'phpCAS'); -/** - * default table when storing PGT's to database - */ -define("CAS_PGT_STORAGE_DB_DEFAULT_TABLE",'pgt'); - -/** @} */ -// ------------------------------------------------------------------------ -// SERVICE ACCESS ERRORS -// ------------------------------------------------------------------------ - /** - * @addtogroup publicServices - * @{ - */ - -/** - * phpCAS::service() error code on success - */ -define("PHPCAS_SERVICE_OK",0); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the CAS server did not respond. - */ -define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE",1); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the response of the CAS server was ill-formed. - */ -define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE",2); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the CAS server did not want to. - */ -define("PHPCAS_SERVICE_PT_FAILURE",3); -/** - * phpCAS::service() error code when the service was not available. - */ -define("PHPCAS_SERVICE_NOT AVAILABLE",4); - -/** @} */ -// ------------------------------------------------------------------------ -// LANGUAGES -// ------------------------------------------------------------------------ - /** - * @addtogroup publicLang - * @{ - */ - -define("PHPCAS_LANG_ENGLISH", 'english'); -define("PHPCAS_LANG_FRENCH", 'french'); -define("PHPCAS_LANG_GREEK", 'greek'); -define("PHPCAS_LANG_GERMAN", 'german'); -define("PHPCAS_LANG_JAPANESE", 'japanese'); -define("PHPCAS_LANG_SPANISH", 'spanish'); -define("PHPCAS_LANG_CATALAN", 'catalan'); - -/** @} */ - -/** - * @addtogroup internalLang - * @{ - */ - -/** - * phpCAS default language (when phpCAS::setLang() is not used) - */ -define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); - -/** @} */ -// ------------------------------------------------------------------------ -// DEBUG -// ------------------------------------------------------------------------ - /** - * @addtogroup publicDebug - * @{ - */ - -/** - * The default directory for the debug file under Unix. - */ -define('DEFAULT_DEBUG_DIR','/tmp/'); - -/** @} */ -// ------------------------------------------------------------------------ -// MISC -// ------------------------------------------------------------------------ - /** - * @addtogroup internalMisc - * @{ - */ - -/** - * This global variable is used by the interface class phpCAS. - * - * @hideinitializer - */ -$GLOBALS['PHPCAS_CLIENT'] = null; - -/** - * This global variable is used to store where the initializer is called from - * (to print a comprehensive error in case of multiple calls). - * - * @hideinitializer - */ -$GLOBALS['PHPCAS_INIT_CALL'] = array('done' => FALSE, - 'file' => '?', - 'line' => -1, - 'method' => '?'); - -/** - * This global variable is used to store where the method checking - * the authentication is called from (to print comprehensive errors) - * - * @hideinitializer - */ -$GLOBALS['PHPCAS_AUTH_CHECK_CALL'] = array('done' => FALSE, - 'file' => '?', - 'line' => -1, - 'method' => '?', - 'result' => FALSE); - -/** - * This global variable is used to store phpCAS debug mode. - * - * @hideinitializer - */ -$GLOBALS['PHPCAS_DEBUG'] = array('filename' => FALSE, - 'indent' => 0, - 'unique_id' => ''); - -/** @} */ - -// ######################################################################## -// CLIENT CLASS -// ######################################################################## - -// include client class -include_once(dirname(__FILE__).'/CAS/client.php'); - -// ######################################################################## -// INTERFACE CLASS -// ######################################################################## - -/** - * @class phpCAS - * The phpCAS class is a simple container for the phpCAS library. It provides CAS - * authentication for web applications written in PHP. - * - * @ingroup public - * @author Pascal Aubry - * - * \internal All its methods access the same object ($PHPCAS_CLIENT, declared - * at the end of CAS/client.php). - */ - - - -class phpCAS -{ - - // ######################################################################## - // INITIALIZATION - // ######################################################################## - - /** - * @addtogroup publicInit - * @{ - */ - - /** - * phpCAS client initializer. - * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be - * called, only once, and before all other methods (except phpCAS::getVersion() - * and phpCAS::setDebug()). - * - * @param $server_version the version of the CAS server - * @param $server_hostname the hostname of the CAS server - * @param $server_port the port the CAS server is running on - * @param $server_uri the URI the CAS server is responding on - * @param $start_session Have phpCAS start PHP sessions (default true) - * - * @return a newly created CASClient object - */ - function client($server_version, - $server_hostname, - $server_port, - $server_uri, - $start_session = true) - { - global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL; - - phpCAS::traceBegin(); - if ( is_object($PHPCAS_CLIENT) ) { - phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')'); - } - if ( gettype($server_version) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_version (should be `string\')'); - } - if ( gettype($server_hostname) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')'); - } - if ( gettype($server_port) != 'integer' ) { - phpCAS::error('type mismatched for parameter $server_port (should be `integer\')'); - } - if ( gettype($server_uri) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_uri (should be `string\')'); - } - - // store where the initialzer is called from - $dbg = phpCAS::backtrace(); - $PHPCAS_INIT_CALL = array('done' => TRUE, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__.'::'.__FUNCTION__); - - // initialize the global object $PHPCAS_CLIENT - $PHPCAS_CLIENT = new CASClient($server_version,FALSE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session); - phpCAS::traceEnd(); - } - - /** - * phpCAS proxy initializer. - * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be - * called, only once, and before all other methods (except phpCAS::getVersion() - * and phpCAS::setDebug()). - * - * @param $server_version the version of the CAS server - * @param $server_hostname the hostname of the CAS server - * @param $server_port the port the CAS server is running on - * @param $server_uri the URI the CAS server is responding on - * @param $start_session Have phpCAS start PHP sessions (default true) - * - * @return a newly created CASClient object - */ - function proxy($server_version, - $server_hostname, - $server_port, - $server_uri, - $start_session = true) - { - global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL; - - phpCAS::traceBegin(); - if ( is_object($PHPCAS_CLIENT) ) { - phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')'); - } - if ( gettype($server_version) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_version (should be `string\')'); - } - if ( gettype($server_hostname) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')'); - } - if ( gettype($server_port) != 'integer' ) { - phpCAS::error('type mismatched for parameter $server_port (should be `integer\')'); - } - if ( gettype($server_uri) != 'string' ) { - phpCAS::error('type mismatched for parameter $server_uri (should be `string\')'); - } - - // store where the initialzer is called from - $dbg = phpCAS::backtrace(); - $PHPCAS_INIT_CALL = array('done' => TRUE, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__.'::'.__FUNCTION__); - - // initialize the global object $PHPCAS_CLIENT - $PHPCAS_CLIENT = new CASClient($server_version,TRUE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session); - phpCAS::traceEnd(); - } - - /** @} */ - // ######################################################################## - // DEBUGGING - // ######################################################################## - - /** - * @addtogroup publicDebug - * @{ - */ - - /** - * Set/unset debug mode - * - * @param $filename the name of the file used for logging, or FALSE to stop debugging. - */ - function setDebug($filename='') - { - global $PHPCAS_DEBUG; - - if ( $filename != FALSE && gettype($filename) != 'string' ) { - phpCAS::error('type mismatched for parameter $dbg (should be FALSE or the name of the log file)'); - } - - if ( empty($filename) ) { - if ( preg_match('/^Win.*/',getenv('OS')) ) { - if ( isset($_ENV['TMP']) ) { - $debugDir = $_ENV['TMP'].'/'; - } else if ( isset($_ENV['TEMP']) ) { - $debugDir = $_ENV['TEMP'].'/'; - } else { - $debugDir = ''; - } - } else { - $debugDir = DEFAULT_DEBUG_DIR; - } - $filename = $debugDir . 'phpCAS.log'; - } - - if ( empty($PHPCAS_DEBUG['unique_id']) ) { - $PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))),0,4); - } - - $PHPCAS_DEBUG['filename'] = $filename; - - phpCAS::trace('START ******************'); - } - - /** @} */ - /** - * @addtogroup internalDebug - * @{ - */ - - /** - * This method is a wrapper for debug_backtrace() that is not available - * in all PHP versions (>= 4.3.0 only) - */ - function backtrace() - { - if ( function_exists('debug_backtrace') ) { - return debug_backtrace(); - } else { - // poor man's hack ... but it does work ... - return array(); - } - } - - /** - * Logs a string in debug mode. - * - * @param $str the string to write - * - * @private - */ - function log($str) - { - $indent_str = "."; - global $PHPCAS_DEBUG; - - if ( $PHPCAS_DEBUG['filename'] ) { - for ($i=0;$i<$PHPCAS_DEBUG['indent'];$i++) { - $indent_str .= '| '; - } - error_log($PHPCAS_DEBUG['unique_id'].' '.$indent_str.$str."\n",3,$PHPCAS_DEBUG['filename']); - } - - } - - /** - * This method is used by interface methods to print an error and where the function - * was originally called from. - * - * @param $msg the message to print - * - * @private - */ - function error($msg) - { - $dbg = phpCAS::backtrace(); - $function = '?'; - $file = '?'; - $line = '?'; - if ( is_array($dbg) ) { - for ( $i=1; $i\nphpCAS error: ".__CLASS__."::".$function.'(): '.htmlentities($msg)." in ".$file." on line ".$line."
\n"; - phpCAS::trace($msg); - phpCAS::traceExit(); - exit(); - } - - /** - * This method is used to log something in debug mode. - */ - function trace($str) - { - $dbg = phpCAS::backtrace(); - phpCAS::log($str.' ['.basename($dbg[1]['file']).':'.$dbg[1]['line'].']'); - } - - /** - * This method is used to indicate the start of the execution of a function in debug mode. - */ - function traceBegin() - { - global $PHPCAS_DEBUG; - - $dbg = phpCAS::backtrace(); - $str = '=> '; - if ( !empty($dbg[2]['class']) ) { - $str .= $dbg[2]['class'].'::'; - } - $str .= $dbg[2]['function'].'('; - if ( is_array($dbg[2]['args']) ) { - foreach ($dbg[2]['args'] as $index => $arg) { - if ( $index != 0 ) { - $str .= ', '; - } - $str .= str_replace("\n","",var_export($arg,TRUE)); - } - } - $str .= ') ['.basename($dbg[2]['file']).':'.$dbg[2]['line'].']'; - phpCAS::log($str); - $PHPCAS_DEBUG['indent'] ++; - } - - /** - * This method is used to indicate the end of the execution of a function in debug mode. - * - * @param $res the result of the function - */ - function traceEnd($res='') - { - global $PHPCAS_DEBUG; - - $PHPCAS_DEBUG['indent'] --; - $dbg = phpCAS::backtrace(); - $str = ''; - $str .= '<= '.str_replace("\n","",var_export($res,TRUE)); - phpCAS::log($str); - } - - /** - * This method is used to indicate the end of the execution of the program - */ - function traceExit() - { - global $PHPCAS_DEBUG; - - phpCAS::log('exit()'); - while ( $PHPCAS_DEBUG['indent'] > 0 ) { - phpCAS::log('-'); - $PHPCAS_DEBUG['indent'] --; - } - } - - /** @} */ - // ######################################################################## - // INTERNATIONALIZATION - // ######################################################################## - /** - * @addtogroup publicLang - * @{ - */ - - /** - * This method is used to set the language used by phpCAS. - * @note Can be called only once. - * - * @param $lang a string representing the language. - * - * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH - */ - function setLang($lang) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - if ( gettype($lang) != 'string' ) { - phpCAS::error('type mismatched for parameter $lang (should be `string\')'); - } - $PHPCAS_CLIENT->setLang($lang); - } - - /** @} */ - // ######################################################################## - // VERSION - // ######################################################################## - /** - * @addtogroup public - * @{ - */ - - /** - * This method returns the phpCAS version. - * - * @return the phpCAS version. - */ - function getVersion() - { - return PHPCAS_VERSION; - } - - /** @} */ - // ######################################################################## - // HTML OUTPUT - // ######################################################################## - /** - * @addtogroup publicOutput - * @{ - */ - - /** - * This method sets the HTML header used for all outputs. - * - * @param $header the HTML header. - */ - function setHTMLHeader($header) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - if ( gettype($header) != 'string' ) { - phpCAS::error('type mismatched for parameter $header (should be `string\')'); - } - $PHPCAS_CLIENT->setHTMLHeader($header); - } - - /** - * This method sets the HTML footer used for all outputs. - * - * @param $footer the HTML footer. - */ - function setHTMLFooter($footer) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - if ( gettype($footer) != 'string' ) { - phpCAS::error('type mismatched for parameter $footer (should be `string\')'); - } - $PHPCAS_CLIENT->setHTMLFooter($footer); - } - - /** @} */ - // ######################################################################## - // PGT STORAGE - // ######################################################################## - /** - * @addtogroup publicPGTStorage - * @{ - */ - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests onto the filesystem. - * - * @param $format the format used to store the PGT's (`plain' and `xml' allowed) - * @param $path the path where the PGT's should be stored - */ - function setPGTStorageFile($format='', - $path='') - { - global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_CLIENT->isProxy() ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) { - phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')'); - } - if ( gettype($format) != 'string' ) { - phpCAS::error('type mismatched for parameter $format (should be `string\')'); - } - if ( gettype($path) != 'string' ) { - phpCAS::error('type mismatched for parameter $format (should be `string\')'); - } - $PHPCAS_CLIENT->setPGTStorageFile($format,$path); - phpCAS::traceEnd(); - } - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests into a database. - * @note The connection to the database is done only when needed. - * As a consequence, bad parameters are detected only when - * initializing PGT storage, except in debug mode. - * - * @param $user the user to access the data with - * @param $password the user's password - * @param $database_type the type of the database hosting the data - * @param $hostname the server hosting the database - * @param $port the port the server is listening on - * @param $database the name of the database - * @param $table the name of the table storing the data - */ - function setPGTStorageDB($user, - $password, - $database_type='', - $hostname='', - $port=0, - $database='', - $table='') - { - global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_CLIENT->isProxy() ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) { - phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')'); - } - if ( gettype($user) != 'string' ) { - phpCAS::error('type mismatched for parameter $user (should be `string\')'); - } - if ( gettype($password) != 'string' ) { - phpCAS::error('type mismatched for parameter $password (should be `string\')'); - } - if ( gettype($database_type) != 'string' ) { - phpCAS::error('type mismatched for parameter $database_type (should be `string\')'); - } - if ( gettype($hostname) != 'string' ) { - phpCAS::error('type mismatched for parameter $hostname (should be `string\')'); - } - if ( gettype($port) != 'integer' ) { - phpCAS::error('type mismatched for parameter $port (should be `integer\')'); - } - if ( gettype($database) != 'string' ) { - phpCAS::error('type mismatched for parameter $database (should be `string\')'); - } - if ( gettype($table) != 'string' ) { - phpCAS::error('type mismatched for parameter $table (should be `string\')'); - } - $PHPCAS_CLIENT->setPGTStorageDB($this,$user,$password,$hostname,$port,$database,$table); - phpCAS::traceEnd(); - } - - /** @} */ - // ######################################################################## - // ACCESS TO EXTERNAL SERVICES - // ######################################################################## - /** - * @addtogroup publicServices - * @{ - */ - - /** - * This method is used to access an HTTP[S] service. - * - * @param $url the service to access. - * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on - * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. - * @param $output the output of the service (also used to give an error - * message on failure). - * - * @return TRUE on success, FALSE otherwise (in this later case, $err_code - * gives the reason why it failed and $output contains an error message). - */ - function serviceWeb($url,&$err_code,&$output) - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_CLIENT->isProxy() ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { - phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { - phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - - $res = $PHPCAS_CLIENT->serviceWeb($url,$err_code,$output); - - phpCAS::traceEnd($res); - return $res; - } - - /** - * This method is used to access an IMAP/POP3/NNTP service. - * - * @param $url a string giving the URL of the service, including the mailing box - * for IMAP URLs, as accepted by imap_open(). - * @param $flags options given to imap_open(). - * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on - * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. - * @param $err_msg an error message on failure - * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL - * on success, FALSE on error). - * - * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code - * gives the reason why it failed and $err_msg contains an error message). - */ - function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt) - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_CLIENT->isProxy() ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { - phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { - phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - - if ( gettype($flags) != 'integer' ) { - phpCAS::error('type mismatched for parameter $flags (should be `integer\')'); - } - - $res = $PHPCAS_CLIENT->serviceMail($url,$flags,$err_code,$err_msg,$pt); - - phpCAS::traceEnd($res); - return $res; - } - - /** @} */ - // ######################################################################## - // AUTHENTICATION - // ######################################################################## - /** - * @addtogroup publicAuth - * @{ - */ - - /** - * Set the times authentication will be cached before really accessing the CAS server in gateway mode: - * - -1: check only once, and then never again (until you pree login) - * - 0: always check - * - n: check every "n" time - * - * @param $n an integer. - */ - function setCacheTimesForAuthRecheck($n) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - if ( gettype($n) != 'integer' ) { - phpCAS::error('type mismatched for parameter $header (should be `string\')'); - } - $PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); - } - - /** - * This method is called to check if the user is authenticated (use the gateway feature). - * @return TRUE when the user is authenticated; otherwise FALSE. - */ - function checkAuthentication() - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - - $auth = $PHPCAS_CLIENT->checkAuthentication(); - - // store where the authentication has been checked and the result - $dbg = phpCAS::backtrace(); - $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__.'::'.__FUNCTION__, - 'result' => $auth ); - phpCAS::traceEnd($auth); - return $auth; - } - - /** - * This method is called to force authentication if the user was not already - * authenticated. If the user is not authenticated, halt by redirecting to - * the CAS server. - */ - function forceAuthentication() - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - - $auth = $PHPCAS_CLIENT->forceAuthentication(); - - // store where the authentication has been checked and the result - $dbg = phpCAS::backtrace(); - $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__.'::'.__FUNCTION__, - 'result' => $auth ); - - if ( !$auth ) { - phpCAS::trace('user is not authenticated, redirecting to the CAS server'); - $PHPCAS_CLIENT->forceAuthentication(); - } else { - phpCAS::trace('no need to authenticate (user `'.phpCAS::getUser().'\' is already authenticated)'); - } - - phpCAS::traceEnd(); - return $auth; - } - - /** - * This method is called to renew the authentication. - **/ - function renewAuthentication() { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before'.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - - // store where the authentication has been checked and the result - $dbg = phpCAS::backtrace(); - $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, 'file' => $dbg[0]['file'], 'line' => $dbg[0]['line'], 'method' => __CLASS__.'::'.__FUNCTION__, 'result' => $auth ); - - $PHPCAS_CLIENT->renewAuthentication(); - phpCAS::traceEnd(); - } - - /** - * This method has been left from version 0.4.1 for compatibility reasons. - */ - function authenticate() - { - phpCAS::error('this method is deprecated. You should use '.__CLASS__.'::forceAuthentication() instead'); - } - - /** - * This method is called to check if the user is authenticated (previously or by - * tickets given in the URL). - * - * @return TRUE when the user is authenticated. - */ - function isAuthenticated() - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - - // call the isAuthenticated method of the global $PHPCAS_CLIENT object - $auth = $PHPCAS_CLIENT->isAuthenticated(); - - // store where the authentication has been checked and the result - $dbg = phpCAS::backtrace(); - $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__.'::'.__FUNCTION__, - 'result' => $auth ); - phpCAS::traceEnd($auth); - return $auth; - } - - /** - * Checks whether authenticated based on $_SESSION. Useful to avoid - * server calls. - * @return true if authenticated, false otherwise. - * @since 0.4.22 by Brendan Arnold - */ - function isSessionAuthenticated () - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - return($PHPCAS_CLIENT->isSessionAuthenticated()); - } - - /** - * This method returns the CAS user's login name. - * @warning should not be called only after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - * - * @return the login name of the authenticated user - */ - function getUser() - { - global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'); - } - if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { - phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); - } - return $PHPCAS_CLIENT->getUser(); - } - - /** - * Handle logout requests. - */ - function handleLogoutRequests($check_client=true, $allowed_clients=false) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - return($PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); - } - - /** - * This method returns the URL to be used to login. - * or phpCAS::isAuthenticated(). - * - * @return the login name of the authenticated user - */ - function getServerLoginURL() - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - return $PHPCAS_CLIENT->getServerLoginURL(); - } - - /** - * Set the login URL of the CAS server. - * @param $url the login URL - * @since 0.4.21 by Wyman Chan - */ - function setServerLoginURL($url='') - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after - '.__CLASS__.'::client()'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be - `string\')'); - } - $PHPCAS_CLIENT->setServerLoginURL($url); - phpCAS::traceEnd(); - } - - /** - * This method returns the URL to be used to login. - * or phpCAS::isAuthenticated(). - * - * @return the login name of the authenticated user - */ - function getServerLogoutURL() - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); - } - return $PHPCAS_CLIENT->getServerLogoutURL(); - } - - /** - * Set the logout URL of the CAS server. - * @param $url the logout URL - * @since 0.4.21 by Wyman Chan - */ - function setServerLogoutURL($url='') - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after - '.__CLASS__.'::client()'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be - `string\')'); - } - $PHPCAS_CLIENT->setServerLogoutURL($url); - phpCAS::traceEnd(); - } - - /** - * This method is used to logout from CAS. - * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server - * @public - */ - function logout($params = "") { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if (!is_object($PHPCAS_CLIENT)) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - $parsedParams = array(); - if ($params != "") { - if (is_string($params)) { - phpCAS::error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); - } - if (!is_array($params)) { - phpCAS::error('type mismatched for parameter $params (should be `array\')'); - } - foreach ($params as $key => $value) { - if ($key != "service" && $key != "url") { - phpCAS::error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); - } - $parsedParams[$key] = $value; - } - } - $PHPCAS_CLIENT->logout($parsedParams); - // never reached - phpCAS::traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS server. - * @param $service a URL that will be transmitted to the CAS server - */ - function logoutWithRedirectService($service) { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - if (!is_string($service)) { - phpCAS::error('type mismatched for parameter $service (should be `string\')'); - } - $PHPCAS_CLIENT->logout(array("service" => $service)); - // never reached - phpCAS::traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS server. - * @param $url a URL that will be transmitted to the CAS server - */ - function logoutWithUrl($url) { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - if (!is_string($url)) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - $PHPCAS_CLIENT->logout(array("url" => $url)); - // never reached - phpCAS::traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS server. - * @param $service a URL that will be transmitted to the CAS server - * @param $url a URL that will be transmitted to the CAS server - */ - function logoutWithRedirectServiceAndUrl($service, $url) { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - if (!is_string($service)) { - phpCAS::error('type mismatched for parameter $service (should be `string\')'); - } - if (!is_string($url)) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - $PHPCAS_CLIENT->logout(array("service" => $service, "url" => $url)); - // never reached - phpCAS::traceEnd(); - } - - /** - * Set the fixed URL that will be used by the CAS server to transmit the PGT. - * When this method is not called, a phpCAS script uses its own URL for the callback. - * - * @param $url the URL - */ - function setFixedCallbackURL($url='') - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( !$PHPCAS_CLIENT->isProxy() ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - $PHPCAS_CLIENT->setCallbackURL($url); - phpCAS::traceEnd(); - } - - /** - * Set the fixed URL that will be set as the CAS service parameter. When this - * method is not called, a phpCAS script uses its own URL. - * - * @param $url the URL - */ - function setFixedServiceURL($url) - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( gettype($url) != 'string' ) { - phpCAS::error('type mismatched for parameter $url (should be `string\')'); - } - $PHPCAS_CLIENT->setURL($url); - phpCAS::traceEnd(); - } - - /** - * Get the URL that is set as the CAS service parameter. - */ - function getServiceURL() - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - return($PHPCAS_CLIENT->getURL()); - } - - /** - * Retrieve a Proxy Ticket from the CAS server. - */ - function retrievePT($target_service,&$err_code,&$err_msg) - { - global $PHPCAS_CLIENT; - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); - } - if ( gettype($target_service) != 'string' ) { - phpCAS::error('type mismatched for parameter $target_service(should be `string\')'); - } - return($PHPCAS_CLIENT->retrievePT($target_service,$err_code,$err_msg)); - } - - /** - * Set the certificate of the CAS server. - * - * @param $cert the PEM certificate - */ - function setCasServerCert($cert) - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - if ( gettype($cert) != 'string' ) { - phpCAS::error('type mismatched for parameter $cert (should be `string\')'); - } - $PHPCAS_CLIENT->setCasServerCert($cert); - phpCAS::traceEnd(); - } - - /** - * Set the certificate of the CAS server CA. - * - * @param $cert the CA certificate - */ - function setCasServerCACert($cert) - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - if ( gettype($cert) != 'string' ) { - phpCAS::error('type mismatched for parameter $cert (should be `string\')'); - } - $PHPCAS_CLIENT->setCasServerCACert($cert); - phpCAS::traceEnd(); - } - - /** - * Set no SSL validation for the CAS server. - */ - function setNoCasServerValidation() - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - $PHPCAS_CLIENT->setNoCasServerValidation(); - phpCAS::traceEnd(); - } - - /** @} */ - - /** - * Change CURL options. - * CURL is used to connect through HTTPS to CAS server - * @param $key the option key - * @param $value the value to set - */ - function setExtraCurlOption($key, $value) - { - global $PHPCAS_CLIENT; - phpCAS::traceBegin(); - if ( !is_object($PHPCAS_CLIENT) ) { - phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); - } - $PHPCAS_CLIENT->setExtraCurlOption($key, $value); - phpCAS::traceEnd(); - } - -} - -// ######################################################################## -// DOCUMENTATION -// ######################################################################## - -// ######################################################################## -// MAIN PAGE - -/** - * @mainpage - * - * The following pages only show the source documentation. - * - */ - -// ######################################################################## -// MODULES DEFINITION - -/** @defgroup public User interface */ - -/** @defgroup publicInit Initialization - * @ingroup public */ - -/** @defgroup publicAuth Authentication - * @ingroup public */ - -/** @defgroup publicServices Access to external services - * @ingroup public */ - -/** @defgroup publicConfig Configuration - * @ingroup public */ - -/** @defgroup publicLang Internationalization - * @ingroup publicConfig */ - -/** @defgroup publicOutput HTML output - * @ingroup publicConfig */ - -/** @defgroup publicPGTStorage PGT storage - * @ingroup publicConfig */ - -/** @defgroup publicDebug Debugging - * @ingroup public */ - - -/** @defgroup internal Implementation */ - -/** @defgroup internalAuthentication Authentication - * @ingroup internal */ - -/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) - * @ingroup internal */ - -/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) - * @ingroup internal */ - -/** @defgroup internalPGTStorage PGT storage - * @ingroup internalProxy */ - -/** @defgroup internalPGTStorageDB PGT storage in a database - * @ingroup internalPGTStorage */ - -/** @defgroup internalPGTStorageFile PGT storage on the filesystem - * @ingroup internalPGTStorage */ - -/** @defgroup internalCallback Callback from the CAS server - * @ingroup internalProxy */ - -/** @defgroup internalProxied CAS proxied client features (CAS 2.0, Proxy Tickets) - * @ingroup internal */ - -/** @defgroup internalConfig Configuration - * @ingroup internal */ - -/** @defgroup internalOutput HTML output - * @ingroup internalConfig */ - -/** @defgroup internalLang Internationalization - * @ingroup internalConfig - * - * To add a new language: - * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php - * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php - * - 3. Make the translations - */ - -/** @defgroup internalDebug Debugging - * @ingroup internal */ - -/** @defgroup internalMisc Miscellaneous - * @ingroup internal */ - -// ######################################################################## -// EXAMPLES - -/** - * @example example_simple.php - */ - /** - * @example example_proxy.php - */ - /** - * @example example_proxy2.php - */ - /** - * @example example_lang.php - */ - /** - * @example example_html.php - */ - /** - * @example example_file.php - */ - /** - * @example example_db.php - */ - /** - * @example example_service.php - */ - /** - * @example example_session_proxy.php - */ - /** - * @example example_session_service.php - */ - /** - * @example example_gateway.php - */ - - - -?> +=')) { + require_once(dirname(__FILE__).'/CAS/domxml-php4-php5.php'); +} + +/** + * @file CAS/CAS.php + * Interface class of the phpCAS library + * + * @ingroup public + */ + +// ######################################################################## +// CONSTANTS +// ######################################################################## + +// ------------------------------------------------------------------------ +// CAS VERSIONS +// ------------------------------------------------------------------------ + +/** + * phpCAS version. accessible for the user by phpCAS::getVersion(). + */ +define('PHPCAS_VERSION','1.0.1'); + +// ------------------------------------------------------------------------ +// CAS VERSIONS +// ------------------------------------------------------------------------ + /** + * @addtogroup public + * @{ + */ + +/** + * CAS version 1.0 + */ +define("CAS_VERSION_1_0",'1.0'); +/*! + * CAS version 2.0 + */ +define("CAS_VERSION_2_0",'2.0'); + +/** @} */ + /** + * @addtogroup publicPGTStorage + * @{ + */ +// ------------------------------------------------------------------------ +// FILE PGT STORAGE +// ------------------------------------------------------------------------ + /** + * Default path used when storing PGT's to file + */ +define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH",'/tmp'); +/** + * phpCAS::setPGTStorageFile()'s 2nd parameter to write plain text files + */ +define("CAS_PGT_STORAGE_FILE_FORMAT_PLAIN",'plain'); +/** + * phpCAS::setPGTStorageFile()'s 2nd parameter to write xml files + */ +define("CAS_PGT_STORAGE_FILE_FORMAT_XML",'xml'); +/** + * Default format used when storing PGT's to file + */ +define("CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT",CAS_PGT_STORAGE_FILE_FORMAT_PLAIN); +// ------------------------------------------------------------------------ +// DATABASE PGT STORAGE +// ------------------------------------------------------------------------ + /** + * default database type when storing PGT's to database + */ +define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE",'mysql'); +/** + * default host when storing PGT's to database + */ +define("CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME",'localhost'); +/** + * default port when storing PGT's to database + */ +define("CAS_PGT_STORAGE_DB_DEFAULT_PORT",''); +/** + * default database when storing PGT's to database + */ +define("CAS_PGT_STORAGE_DB_DEFAULT_DATABASE",'phpCAS'); +/** + * default table when storing PGT's to database + */ +define("CAS_PGT_STORAGE_DB_DEFAULT_TABLE",'pgt'); + +/** @} */ +// ------------------------------------------------------------------------ +// SERVICE ACCESS ERRORS +// ------------------------------------------------------------------------ + /** + * @addtogroup publicServices + * @{ + */ + +/** + * phpCAS::service() error code on success + */ +define("PHPCAS_SERVICE_OK",0); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not respond. + */ +define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE",1); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the response of the CAS server was ill-formed. + */ +define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE",2); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not want to. + */ +define("PHPCAS_SERVICE_PT_FAILURE",3); +/** + * phpCAS::service() error code when the service was not available. + */ +define("PHPCAS_SERVICE_NOT AVAILABLE",4); + +/** @} */ +// ------------------------------------------------------------------------ +// LANGUAGES +// ------------------------------------------------------------------------ + /** + * @addtogroup publicLang + * @{ + */ + +define("PHPCAS_LANG_ENGLISH", 'english'); +define("PHPCAS_LANG_FRENCH", 'french'); +define("PHPCAS_LANG_GREEK", 'greek'); +define("PHPCAS_LANG_GERMAN", 'german'); +define("PHPCAS_LANG_JAPANESE", 'japanese'); +define("PHPCAS_LANG_SPANISH", 'spanish'); +define("PHPCAS_LANG_CATALAN", 'catalan'); + +/** @} */ + +/** + * @addtogroup internalLang + * @{ + */ + +/** + * phpCAS default language (when phpCAS::setLang() is not used) + */ +define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); + +/** @} */ +// ------------------------------------------------------------------------ +// DEBUG +// ------------------------------------------------------------------------ + /** + * @addtogroup publicDebug + * @{ + */ + +/** + * The default directory for the debug file under Unix. + */ +define('DEFAULT_DEBUG_DIR','/tmp/'); + +/** @} */ +// ------------------------------------------------------------------------ +// MISC +// ------------------------------------------------------------------------ + /** + * @addtogroup internalMisc + * @{ + */ + +/** + * This global variable is used by the interface class phpCAS. + * + * @hideinitializer + */ +$GLOBALS['PHPCAS_CLIENT'] = null; + +/** + * This global variable is used to store where the initializer is called from + * (to print a comprehensive error in case of multiple calls). + * + * @hideinitializer + */ +$GLOBALS['PHPCAS_INIT_CALL'] = array('done' => FALSE, + 'file' => '?', + 'line' => -1, + 'method' => '?'); + +/** + * This global variable is used to store where the method checking + * the authentication is called from (to print comprehensive errors) + * + * @hideinitializer + */ +$GLOBALS['PHPCAS_AUTH_CHECK_CALL'] = array('done' => FALSE, + 'file' => '?', + 'line' => -1, + 'method' => '?', + 'result' => FALSE); + +/** + * This global variable is used to store phpCAS debug mode. + * + * @hideinitializer + */ +$GLOBALS['PHPCAS_DEBUG'] = array('filename' => FALSE, + 'indent' => 0, + 'unique_id' => ''); + +/** @} */ + +// ######################################################################## +// CLIENT CLASS +// ######################################################################## + +// include client class +include_once(dirname(__FILE__).'/CAS/client.php'); + +// ######################################################################## +// INTERFACE CLASS +// ######################################################################## + +/** + * @class phpCAS + * The phpCAS class is a simple container for the phpCAS library. It provides CAS + * authentication for web applications written in PHP. + * + * @ingroup public + * @author Pascal Aubry + * + * \internal All its methods access the same object ($PHPCAS_CLIENT, declared + * at the end of CAS/client.php). + */ + + + +class phpCAS +{ + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * @addtogroup publicInit + * @{ + */ + + /** + * phpCAS client initializer. + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + * + * @param $server_version the version of the CAS server + * @param $server_hostname the hostname of the CAS server + * @param $server_port the port the CAS server is running on + * @param $server_uri the URI the CAS server is responding on + * @param $start_session Have phpCAS start PHP sessions (default true) + * + * @return a newly created CASClient object + */ + function client($server_version, + $server_hostname, + $server_port, + $server_uri, + $start_session = true) + { + global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL; + + phpCAS::traceBegin(); + if ( is_object($PHPCAS_CLIENT) ) { + phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')'); + } + if ( gettype($server_version) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_version (should be `string\')'); + } + if ( gettype($server_hostname) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')'); + } + if ( gettype($server_port) != 'integer' ) { + phpCAS::error('type mismatched for parameter $server_port (should be `integer\')'); + } + if ( gettype($server_uri) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_uri (should be `string\')'); + } + + // store where the initialzer is called from + $dbg = phpCAS::backtrace(); + $PHPCAS_INIT_CALL = array('done' => TRUE, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__.'::'.__FUNCTION__); + + // initialize the global object $PHPCAS_CLIENT + $PHPCAS_CLIENT = new CASClient($server_version,FALSE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session); + phpCAS::traceEnd(); + } + + /** + * phpCAS proxy initializer. + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + * + * @param $server_version the version of the CAS server + * @param $server_hostname the hostname of the CAS server + * @param $server_port the port the CAS server is running on + * @param $server_uri the URI the CAS server is responding on + * @param $start_session Have phpCAS start PHP sessions (default true) + * + * @return a newly created CASClient object + */ + function proxy($server_version, + $server_hostname, + $server_port, + $server_uri, + $start_session = true) + { + global $PHPCAS_CLIENT, $PHPCAS_INIT_CALL; + + phpCAS::traceBegin(); + if ( is_object($PHPCAS_CLIENT) ) { + phpCAS::error($PHPCAS_INIT_CALL['method'].'() has already been called (at '.$PHPCAS_INIT_CALL['file'].':'.$PHPCAS_INIT_CALL['line'].')'); + } + if ( gettype($server_version) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_version (should be `string\')'); + } + if ( gettype($server_hostname) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_hostname (should be `string\')'); + } + if ( gettype($server_port) != 'integer' ) { + phpCAS::error('type mismatched for parameter $server_port (should be `integer\')'); + } + if ( gettype($server_uri) != 'string' ) { + phpCAS::error('type mismatched for parameter $server_uri (should be `string\')'); + } + + // store where the initialzer is called from + $dbg = phpCAS::backtrace(); + $PHPCAS_INIT_CALL = array('done' => TRUE, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__.'::'.__FUNCTION__); + + // initialize the global object $PHPCAS_CLIENT + $PHPCAS_CLIENT = new CASClient($server_version,TRUE/*proxy*/,$server_hostname,$server_port,$server_uri,$start_session); + phpCAS::traceEnd(); + } + + /** @} */ + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * @addtogroup publicDebug + * @{ + */ + + /** + * Set/unset debug mode + * + * @param $filename the name of the file used for logging, or FALSE to stop debugging. + */ + function setDebug($filename='') + { + global $PHPCAS_DEBUG; + + if ( $filename != FALSE && gettype($filename) != 'string' ) { + phpCAS::error('type mismatched for parameter $dbg (should be FALSE or the name of the log file)'); + } + + if ( empty($filename) ) { + if ( preg_match('/^Win.*/',getenv('OS')) ) { + if ( isset($_ENV['TMP']) ) { + $debugDir = $_ENV['TMP'].'/'; + } else if ( isset($_ENV['TEMP']) ) { + $debugDir = $_ENV['TEMP'].'/'; + } else { + $debugDir = ''; + } + } else { + $debugDir = DEFAULT_DEBUG_DIR; + } + $filename = $debugDir . 'phpCAS.log'; + } + + if ( empty($PHPCAS_DEBUG['unique_id']) ) { + $PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))),0,4); + } + + $PHPCAS_DEBUG['filename'] = $filename; + + phpCAS::trace('START ******************'); + } + + /** @} */ + /** + * @addtogroup internalDebug + * @{ + */ + + /** + * This method is a wrapper for debug_backtrace() that is not available + * in all PHP versions (>= 4.3.0 only) + */ + function backtrace() + { + if ( function_exists('debug_backtrace') ) { + return debug_backtrace(); + } else { + // poor man's hack ... but it does work ... + return array(); + } + } + + /** + * Logs a string in debug mode. + * + * @param $str the string to write + * + * @private + */ + function log($str) + { + $indent_str = "."; + global $PHPCAS_DEBUG; + + if ( $PHPCAS_DEBUG['filename'] ) { + for ($i=0;$i<$PHPCAS_DEBUG['indent'];$i++) { + $indent_str .= '| '; + } + error_log($PHPCAS_DEBUG['unique_id'].' '.$indent_str.$str."\n",3,$PHPCAS_DEBUG['filename']); + } + + } + + /** + * This method is used by interface methods to print an error and where the function + * was originally called from. + * + * @param $msg the message to print + * + * @private + */ + function error($msg) + { + $dbg = phpCAS::backtrace(); + $function = '?'; + $file = '?'; + $line = '?'; + if ( is_array($dbg) ) { + for ( $i=1; $i\nphpCAS error: ".__CLASS__."::".$function.'(): '.htmlentities($msg)." in ".$file." on line ".$line."
\n"; + phpCAS::trace($msg); + phpCAS::traceExit(); + exit(); + } + + /** + * This method is used to log something in debug mode. + */ + function trace($str) + { + $dbg = phpCAS::backtrace(); + phpCAS::log($str.' ['.basename($dbg[1]['file']).':'.$dbg[1]['line'].']'); + } + + /** + * This method is used to indicate the start of the execution of a function in debug mode. + */ + function traceBegin() + { + global $PHPCAS_DEBUG; + + $dbg = phpCAS::backtrace(); + $str = '=> '; + if ( !empty($dbg[2]['class']) ) { + $str .= $dbg[2]['class'].'::'; + } + $str .= $dbg[2]['function'].'('; + if ( is_array($dbg[2]['args']) ) { + foreach ($dbg[2]['args'] as $index => $arg) { + if ( $index != 0 ) { + $str .= ', '; + } + $str .= str_replace("\n","",var_export($arg,TRUE)); + } + } + $str .= ') ['.basename($dbg[2]['file']).':'.$dbg[2]['line'].']'; + phpCAS::log($str); + $PHPCAS_DEBUG['indent'] ++; + } + + /** + * This method is used to indicate the end of the execution of a function in debug mode. + * + * @param $res the result of the function + */ + function traceEnd($res='') + { + global $PHPCAS_DEBUG; + + $PHPCAS_DEBUG['indent'] --; + $dbg = phpCAS::backtrace(); + $str = ''; + $str .= '<= '.str_replace("\n","",var_export($res,TRUE)); + phpCAS::log($str); + } + + /** + * This method is used to indicate the end of the execution of the program + */ + function traceExit() + { + global $PHPCAS_DEBUG; + + phpCAS::log('exit()'); + while ( $PHPCAS_DEBUG['indent'] > 0 ) { + phpCAS::log('-'); + $PHPCAS_DEBUG['indent'] --; + } + } + + /** @} */ + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup publicLang + * @{ + */ + + /** + * This method is used to set the language used by phpCAS. + * @note Can be called only once. + * + * @param $lang a string representing the language. + * + * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH + */ + function setLang($lang) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + if ( gettype($lang) != 'string' ) { + phpCAS::error('type mismatched for parameter $lang (should be `string\')'); + } + $PHPCAS_CLIENT->setLang($lang); + } + + /** @} */ + // ######################################################################## + // VERSION + // ######################################################################## + /** + * @addtogroup public + * @{ + */ + + /** + * This method returns the phpCAS version. + * + * @return the phpCAS version. + */ + function getVersion() + { + return PHPCAS_VERSION; + } + + /** @} */ + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup publicOutput + * @{ + */ + + /** + * This method sets the HTML header used for all outputs. + * + * @param $header the HTML header. + */ + function setHTMLHeader($header) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + if ( gettype($header) != 'string' ) { + phpCAS::error('type mismatched for parameter $header (should be `string\')'); + } + $PHPCAS_CLIENT->setHTMLHeader($header); + } + + /** + * This method sets the HTML footer used for all outputs. + * + * @param $footer the HTML footer. + */ + function setHTMLFooter($footer) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + if ( gettype($footer) != 'string' ) { + phpCAS::error('type mismatched for parameter $footer (should be `string\')'); + } + $PHPCAS_CLIENT->setHTMLFooter($footer); + } + + /** @} */ + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup publicPGTStorage + * @{ + */ + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param $format the format used to store the PGT's (`plain' and `xml' allowed) + * @param $path the path where the PGT's should be stored + */ + function setPGTStorageFile($format='', + $path='') + { + global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_CLIENT->isProxy() ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) { + phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')'); + } + if ( gettype($format) != 'string' ) { + phpCAS::error('type mismatched for parameter $format (should be `string\')'); + } + if ( gettype($path) != 'string' ) { + phpCAS::error('type mismatched for parameter $format (should be `string\')'); + } + $PHPCAS_CLIENT->setPGTStorageFile($format,$path); + phpCAS::traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests into a database. + * @note The connection to the database is done only when needed. + * As a consequence, bad parameters are detected only when + * initializing PGT storage, except in debug mode. + * + * @param $user the user to access the data with + * @param $password the user's password + * @param $database_type the type of the database hosting the data + * @param $hostname the server hosting the database + * @param $port the port the server is listening on + * @param $database the name of the database + * @param $table the name of the table storing the data + */ + function setPGTStorageDB($user, + $password, + $database_type='', + $hostname='', + $port=0, + $database='', + $table='') + { + global $PHPCAS_CLIENT,$PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_CLIENT->isProxy() ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( $PHPCAS_AUTH_CHECK_CALL['done'] ) { + phpCAS::error('this method should only be called before '.$PHPCAS_AUTH_CHECK_CALL['method'].'() (called at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].')'); + } + if ( gettype($user) != 'string' ) { + phpCAS::error('type mismatched for parameter $user (should be `string\')'); + } + if ( gettype($password) != 'string' ) { + phpCAS::error('type mismatched for parameter $password (should be `string\')'); + } + if ( gettype($database_type) != 'string' ) { + phpCAS::error('type mismatched for parameter $database_type (should be `string\')'); + } + if ( gettype($hostname) != 'string' ) { + phpCAS::error('type mismatched for parameter $hostname (should be `string\')'); + } + if ( gettype($port) != 'integer' ) { + phpCAS::error('type mismatched for parameter $port (should be `integer\')'); + } + if ( gettype($database) != 'string' ) { + phpCAS::error('type mismatched for parameter $database (should be `string\')'); + } + if ( gettype($table) != 'string' ) { + phpCAS::error('type mismatched for parameter $table (should be `string\')'); + } + $PHPCAS_CLIENT->setPGTStorageDB($this,$user,$password,$hostname,$port,$database,$table); + phpCAS::traceEnd(); + } + + /** @} */ + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + /** + * @addtogroup publicServices + * @{ + */ + + /** + * This method is used to access an HTTP[S] service. + * + * @param $url the service to access. + * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on + * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. + * @param $output the output of the service (also used to give an error + * message on failure). + * + * @return TRUE on success, FALSE otherwise (in this later case, $err_code + * gives the reason why it failed and $output contains an error message). + */ + function serviceWeb($url,&$err_code,&$output) + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_CLIENT->isProxy() ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { + phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { + phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + + $res = $PHPCAS_CLIENT->serviceWeb($url,$err_code,$output); + + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param $url a string giving the URL of the service, including the mailing box + * for IMAP URLs, as accepted by imap_open(). + * @param $flags options given to imap_open(). + * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on + * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. + * @param $err_msg an error message on failure + * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL + * on success, FALSE on error). + * + * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code + * gives the reason why it failed and $err_msg contains an error message). + */ + function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt) + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_CLIENT->isProxy() ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { + phpCAS::error('this method should only be called after the programmer is sure the user has been authenticated (by calling '.__CLASS__.'::checkAuthentication() or '.__CLASS__.'::forceAuthentication()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { + phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + + if ( gettype($flags) != 'integer' ) { + phpCAS::error('type mismatched for parameter $flags (should be `integer\')'); + } + + $res = $PHPCAS_CLIENT->serviceMail($url,$flags,$err_code,$err_msg,$pt); + + phpCAS::traceEnd($res); + return $res; + } + + /** @} */ + // ######################################################################## + // AUTHENTICATION + // ######################################################################## + /** + * @addtogroup publicAuth + * @{ + */ + + /** + * Set the times authentication will be cached before really accessing the CAS server in gateway mode: + * - -1: check only once, and then never again (until you pree login) + * - 0: always check + * - n: check every "n" time + * + * @param $n an integer. + */ + function setCacheTimesForAuthRecheck($n) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + if ( gettype($n) != 'integer' ) { + phpCAS::error('type mismatched for parameter $header (should be `string\')'); + } + $PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } + + /** + * This method is called to check if the user is authenticated (use the gateway feature). + * @return TRUE when the user is authenticated; otherwise FALSE. + */ + function checkAuthentication() + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + + $auth = $PHPCAS_CLIENT->checkAuthentication(); + + // store where the authentication has been checked and the result + $dbg = phpCAS::backtrace(); + $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__.'::'.__FUNCTION__, + 'result' => $auth ); + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * This method is called to force authentication if the user was not already + * authenticated. If the user is not authenticated, halt by redirecting to + * the CAS server. + */ + function forceAuthentication() + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + + $auth = $PHPCAS_CLIENT->forceAuthentication(); + + // store where the authentication has been checked and the result + $dbg = phpCAS::backtrace(); + $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__.'::'.__FUNCTION__, + 'result' => $auth ); + + if ( !$auth ) { + phpCAS::trace('user is not authenticated, redirecting to the CAS server'); + $PHPCAS_CLIENT->forceAuthentication(); + } else { + phpCAS::trace('no need to authenticate (user `'.phpCAS::getUser().'\' is already authenticated)'); + } + + phpCAS::traceEnd(); + return $auth; + } + + /** + * This method is called to renew the authentication. + **/ + function renewAuthentication() { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before'.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + + // store where the authentication has been checked and the result + $dbg = phpCAS::backtrace(); + $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, 'file' => $dbg[0]['file'], 'line' => $dbg[0]['line'], 'method' => __CLASS__.'::'.__FUNCTION__, 'result' => $auth ); + + $PHPCAS_CLIENT->renewAuthentication(); + phpCAS::traceEnd(); + } + + /** + * This method has been left from version 0.4.1 for compatibility reasons. + */ + function authenticate() + { + phpCAS::error('this method is deprecated. You should use '.__CLASS__.'::forceAuthentication() instead'); + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @return TRUE when the user is authenticated. + */ + function isAuthenticated() + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + + // call the isAuthenticated method of the global $PHPCAS_CLIENT object + $auth = $PHPCAS_CLIENT->isAuthenticated(); + + // store where the authentication has been checked and the result + $dbg = phpCAS::backtrace(); + $PHPCAS_AUTH_CHECK_CALL = array('done' => TRUE, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__.'::'.__FUNCTION__, + 'result' => $auth ); + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * Checks whether authenticated based on $_SESSION. Useful to avoid + * server calls. + * @return true if authenticated, false otherwise. + * @since 0.4.22 by Brendan Arnold + */ + function isSessionAuthenticated () + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + return($PHPCAS_CLIENT->isSessionAuthenticated()); + } + + /** + * This method returns the CAS user's login name. + * @warning should not be called only after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return the login name of the authenticated user + */ + function getUser() + { + global $PHPCAS_CLIENT, $PHPCAS_AUTH_CHECK_CALL; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['done'] ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'); + } + if ( !$PHPCAS_AUTH_CHECK_CALL['result'] ) { + phpCAS::error('authentication was checked (by '.$PHPCAS_AUTH_CHECK_CALL['method'].'() at '.$PHPCAS_AUTH_CHECK_CALL['file'].':'.$PHPCAS_AUTH_CHECK_CALL['line'].') but the method returned FALSE'); + } + return $PHPCAS_CLIENT->getUser(); + } + + /** + * Handle logout requests. + */ + function handleLogoutRequests($check_client=true, $allowed_clients=false) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + return($PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); + } + + /** + * This method returns the URL to be used to login. + * or phpCAS::isAuthenticated(). + * + * @return the login name of the authenticated user + */ + function getServerLoginURL() + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + return $PHPCAS_CLIENT->getServerLoginURL(); + } + + /** + * Set the login URL of the CAS server. + * @param $url the login URL + * @since 0.4.21 by Wyman Chan + */ + function setServerLoginURL($url='') + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after + '.__CLASS__.'::client()'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be + `string\')'); + } + $PHPCAS_CLIENT->setServerLoginURL($url); + phpCAS::traceEnd(); + } + + /** + * This method returns the URL to be used to login. + * or phpCAS::isAuthenticated(). + * + * @return the login name of the authenticated user + */ + function getServerLogoutURL() + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should not be called before '.__CLASS__.'::client() or '.__CLASS__.'::proxy()'); + } + return $PHPCAS_CLIENT->getServerLogoutURL(); + } + + /** + * Set the logout URL of the CAS server. + * @param $url the logout URL + * @since 0.4.21 by Wyman Chan + */ + function setServerLogoutURL($url='') + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after + '.__CLASS__.'::client()'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be + `string\')'); + } + $PHPCAS_CLIENT->setServerLogoutURL($url); + phpCAS::traceEnd(); + } + + /** + * This method is used to logout from CAS. + * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server + * @public + */ + function logout($params = "") { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if (!is_object($PHPCAS_CLIENT)) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + $parsedParams = array(); + if ($params != "") { + if (is_string($params)) { + phpCAS::error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); + } + if (!is_array($params)) { + phpCAS::error('type mismatched for parameter $params (should be `array\')'); + } + foreach ($params as $key => $value) { + if ($key != "service" && $key != "url") { + phpCAS::error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); + } + $parsedParams[$key] = $value; + } + } + $PHPCAS_CLIENT->logout($parsedParams); + // never reached + phpCAS::traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS server. + * @param $service a URL that will be transmitted to the CAS server + */ + function logoutWithRedirectService($service) { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + if (!is_string($service)) { + phpCAS::error('type mismatched for parameter $service (should be `string\')'); + } + $PHPCAS_CLIENT->logout(array("service" => $service)); + // never reached + phpCAS::traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS server. + * @param $url a URL that will be transmitted to the CAS server + */ + function logoutWithUrl($url) { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + if (!is_string($url)) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + $PHPCAS_CLIENT->logout(array("url" => $url)); + // never reached + phpCAS::traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS server. + * @param $service a URL that will be transmitted to the CAS server + * @param $url a URL that will be transmitted to the CAS server + */ + function logoutWithRedirectServiceAndUrl($service, $url) { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + if (!is_string($service)) { + phpCAS::error('type mismatched for parameter $service (should be `string\')'); + } + if (!is_string($url)) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + $PHPCAS_CLIENT->logout(array("service" => $service, "url" => $url)); + // never reached + phpCAS::traceEnd(); + } + + /** + * Set the fixed URL that will be used by the CAS server to transmit the PGT. + * When this method is not called, a phpCAS script uses its own URL for the callback. + * + * @param $url the URL + */ + function setFixedCallbackURL($url='') + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( !$PHPCAS_CLIENT->isProxy() ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + $PHPCAS_CLIENT->setCallbackURL($url); + phpCAS::traceEnd(); + } + + /** + * Set the fixed URL that will be set as the CAS service parameter. When this + * method is not called, a phpCAS script uses its own URL. + * + * @param $url the URL + */ + function setFixedServiceURL($url) + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( gettype($url) != 'string' ) { + phpCAS::error('type mismatched for parameter $url (should be `string\')'); + } + $PHPCAS_CLIENT->setURL($url); + phpCAS::traceEnd(); + } + + /** + * Get the URL that is set as the CAS service parameter. + */ + function getServiceURL() + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + return($PHPCAS_CLIENT->getURL()); + } + + /** + * Retrieve a Proxy Ticket from the CAS server. + */ + function retrievePT($target_service,&$err_code,&$err_msg) + { + global $PHPCAS_CLIENT; + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::proxy()'); + } + if ( gettype($target_service) != 'string' ) { + phpCAS::error('type mismatched for parameter $target_service(should be `string\')'); + } + return($PHPCAS_CLIENT->retrievePT($target_service,$err_code,$err_msg)); + } + + /** + * Set the certificate of the CAS server. + * + * @param $cert the PEM certificate + */ + function setCasServerCert($cert) + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + if ( gettype($cert) != 'string' ) { + phpCAS::error('type mismatched for parameter $cert (should be `string\')'); + } + $PHPCAS_CLIENT->setCasServerCert($cert); + phpCAS::traceEnd(); + } + + /** + * Set the certificate of the CAS server CA. + * + * @param $cert the CA certificate + */ + function setCasServerCACert($cert) + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + if ( gettype($cert) != 'string' ) { + phpCAS::error('type mismatched for parameter $cert (should be `string\')'); + } + $PHPCAS_CLIENT->setCasServerCACert($cert); + phpCAS::traceEnd(); + } + + /** + * Set no SSL validation for the CAS server. + */ + function setNoCasServerValidation() + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + $PHPCAS_CLIENT->setNoCasServerValidation(); + phpCAS::traceEnd(); + } + + /** @} */ + + /** + * Change CURL options. + * CURL is used to connect through HTTPS to CAS server + * @param $key the option key + * @param $value the value to set + */ + function setExtraCurlOption($key, $value) + { + global $PHPCAS_CLIENT; + phpCAS::traceBegin(); + if ( !is_object($PHPCAS_CLIENT) ) { + phpCAS::error('this method should only be called after '.__CLASS__.'::client() or'.__CLASS__.'::proxy()'); + } + $PHPCAS_CLIENT->setExtraCurlOption($key, $value); + phpCAS::traceEnd(); + } + +} + +// ######################################################################## +// DOCUMENTATION +// ######################################################################## + +// ######################################################################## +// MAIN PAGE + +/** + * @mainpage + * + * The following pages only show the source documentation. + * + */ + +// ######################################################################## +// MODULES DEFINITION + +/** @defgroup public User interface */ + +/** @defgroup publicInit Initialization + * @ingroup public */ + +/** @defgroup publicAuth Authentication + * @ingroup public */ + +/** @defgroup publicServices Access to external services + * @ingroup public */ + +/** @defgroup publicConfig Configuration + * @ingroup public */ + +/** @defgroup publicLang Internationalization + * @ingroup publicConfig */ + +/** @defgroup publicOutput HTML output + * @ingroup publicConfig */ + +/** @defgroup publicPGTStorage PGT storage + * @ingroup publicConfig */ + +/** @defgroup publicDebug Debugging + * @ingroup public */ + + +/** @defgroup internal Implementation */ + +/** @defgroup internalAuthentication Authentication + * @ingroup internal */ + +/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) + * @ingroup internal */ + +/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) + * @ingroup internal */ + +/** @defgroup internalPGTStorage PGT storage + * @ingroup internalProxy */ + +/** @defgroup internalPGTStorageDB PGT storage in a database + * @ingroup internalPGTStorage */ + +/** @defgroup internalPGTStorageFile PGT storage on the filesystem + * @ingroup internalPGTStorage */ + +/** @defgroup internalCallback Callback from the CAS server + * @ingroup internalProxy */ + +/** @defgroup internalProxied CAS proxied client features (CAS 2.0, Proxy Tickets) + * @ingroup internal */ + +/** @defgroup internalConfig Configuration + * @ingroup internal */ + +/** @defgroup internalOutput HTML output + * @ingroup internalConfig */ + +/** @defgroup internalLang Internationalization + * @ingroup internalConfig + * + * To add a new language: + * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php + * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php + * - 3. Make the translations + */ + +/** @defgroup internalDebug Debugging + * @ingroup internal */ + +/** @defgroup internalMisc Miscellaneous + * @ingroup internal */ + +// ######################################################################## +// EXAMPLES + +/** + * @example example_simple.php + */ + /** + * @example example_proxy.php + */ + /** + * @example example_proxy2.php + */ + /** + * @example example_lang.php + */ + /** + * @example example_html.php + */ + /** + * @example example_file.php + */ + /** + * @example example_db.php + */ + /** + * @example example_service.php + */ + /** + * @example example_session_proxy.php + */ + /** + * @example example_session_service.php + */ + /** + * @example example_gateway.php + */ + + + +?> diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php index 5a589e4b28..00797b9c56 100644 --- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php +++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-db.php @@ -1,190 +1,190 @@ - - * - * @ingroup internalPGTStorageDB - */ - -class PGTStorageDB extends PGTStorage -{ - /** - * @addtogroup internalPGTStorageDB - * @{ - */ - - /** - * a string representing a PEAR DB URL to connect to the database. Written by - * PGTStorageDB::PGTStorageDB(), read by getURL(). - * - * @hideinitializer - * @private - */ - var $_url=''; - - /** - * This method returns the PEAR DB URL to use to connect to the database. - * - * @return a PEAR DB URL - * - * @private - */ - function getURL() - { - return $this->_url; - } - - /** - * The handle of the connection to the database where PGT's are stored. Written by - * PGTStorageDB::init(), read by getLink(). - * - * @hideinitializer - * @private - */ - var $_link = null; - - /** - * This method returns the handle of the connection to the database where PGT's are - * stored. - * - * @return a handle of connection. - * - * @private - */ - function getLink() - { - return $this->_link; - } - - /** - * The name of the table where PGT's are stored. Written by - * PGTStorageDB::PGTStorageDB(), read by getTable(). - * - * @hideinitializer - * @private - */ - var $_table = ''; - - /** - * This method returns the name of the table where PGT's are stored. - * - * @return the name of a table. - * - * @private - */ - function getTable() - { - return $this->_table; - } - - // ######################################################################## - // DEBUGGING - // ######################################################################## - - /** - * This method returns an informational string giving the type of storage - * used by the object (used for debugging purposes). - * - * @return an informational string. - * @public - */ - function getStorageType() - { - return "database"; - } - - /** - * This method returns an informational string giving informations on the - * parameters of the storage.(used for debugging purposes). - * - * @public - */ - function getStorageInfo() - { - return 'url=`'.$this->getURL().'\', table=`'.$this->getTable().'\''; - } - - // ######################################################################## - // CONSTRUCTOR - // ######################################################################## - - /** - * The class constructor, called by CASClient::SetPGTStorageDB(). - * - * @param $cas_parent the CASClient instance that creates the object. - * @param $user the user to access the data with - * @param $password the user's password - * @param $database_type the type of the database hosting the data - * @param $hostname the server hosting the database - * @param $port the port the server is listening on - * @param $database the name of the database - * @param $table the name of the table storing the data - * - * @public - */ - function PGTStorageDB($cas_parent,$user,$password,$database_type,$hostname,$port,$database,$table) - { - phpCAS::traceBegin(); - - // call the ancestor's constructor - $this->PGTStorage($cas_parent); - - if ( empty($database_type) ) $database_type = CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE; - if ( empty($hostname) ) $hostname = CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME; - if ( $port==0 ) $port = CAS_PGT_STORAGE_DB_DEFAULT_PORT; - if ( empty($database) ) $database = CAS_PGT_STORAGE_DB_DEFAULT_DATABASE; - if ( empty($table) ) $table = CAS_PGT_STORAGE_DB_DEFAULT_TABLE; - - // build and store the PEAR DB URL - $this->_url = $database_type.':'.'//'.$user.':'.$password.'@'.$hostname.':'.$port.'/'.$database; - - // XXX should use setURL and setTable - phpCAS::traceEnd(); - } - - // ######################################################################## - // INITIALIZATION - // ######################################################################## - - /** - * This method is used to initialize the storage. Halts on error. - * - * @public - */ - function init() - { - phpCAS::traceBegin(); - // if the storage has already been initialized, return immediatly - if ( $this->isInitialized() ) - return; - // call the ancestor's method (mark as initialized) - parent::init(); - - //include phpDB library (the test was introduced in release 0.4.8 for - //the integration into Tikiwiki). - if (!class_exists('DB')) { - include_once('DB.php'); - } - - // try to connect to the database - $this->_link = DB::connect($this->getURL()); - if ( DB::isError($this->_link) ) { - phpCAS::error('could not connect to database ('.DB::errorMessage($this->_link).')'); - } - var_dump($this->_link); - phpCAS::traceBEnd(); - } - - /** @} */ -} - + + * + * @ingroup internalPGTStorageDB + */ + +class PGTStorageDB extends PGTStorage +{ + /** + * @addtogroup internalPGTStorageDB + * @{ + */ + + /** + * a string representing a PEAR DB URL to connect to the database. Written by + * PGTStorageDB::PGTStorageDB(), read by getURL(). + * + * @hideinitializer + * @private + */ + var $_url=''; + + /** + * This method returns the PEAR DB URL to use to connect to the database. + * + * @return a PEAR DB URL + * + * @private + */ + function getURL() + { + return $this->_url; + } + + /** + * The handle of the connection to the database where PGT's are stored. Written by + * PGTStorageDB::init(), read by getLink(). + * + * @hideinitializer + * @private + */ + var $_link = null; + + /** + * This method returns the handle of the connection to the database where PGT's are + * stored. + * + * @return a handle of connection. + * + * @private + */ + function getLink() + { + return $this->_link; + } + + /** + * The name of the table where PGT's are stored. Written by + * PGTStorageDB::PGTStorageDB(), read by getTable(). + * + * @hideinitializer + * @private + */ + var $_table = ''; + + /** + * This method returns the name of the table where PGT's are stored. + * + * @return the name of a table. + * + * @private + */ + function getTable() + { + return $this->_table; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return an informational string. + * @public + */ + function getStorageType() + { + return "database"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @public + */ + function getStorageInfo() + { + return 'url=`'.$this->getURL().'\', table=`'.$this->getTable().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor, called by CASClient::SetPGTStorageDB(). + * + * @param $cas_parent the CASClient instance that creates the object. + * @param $user the user to access the data with + * @param $password the user's password + * @param $database_type the type of the database hosting the data + * @param $hostname the server hosting the database + * @param $port the port the server is listening on + * @param $database the name of the database + * @param $table the name of the table storing the data + * + * @public + */ + function PGTStorageDB($cas_parent,$user,$password,$database_type,$hostname,$port,$database,$table) + { + phpCAS::traceBegin(); + + // call the ancestor's constructor + $this->PGTStorage($cas_parent); + + if ( empty($database_type) ) $database_type = CAS_PGT_STORAGE_DB_DEFAULT_DATABASE_TYPE; + if ( empty($hostname) ) $hostname = CAS_PGT_STORAGE_DB_DEFAULT_HOSTNAME; + if ( $port==0 ) $port = CAS_PGT_STORAGE_DB_DEFAULT_PORT; + if ( empty($database) ) $database = CAS_PGT_STORAGE_DB_DEFAULT_DATABASE; + if ( empty($table) ) $table = CAS_PGT_STORAGE_DB_DEFAULT_TABLE; + + // build and store the PEAR DB URL + $this->_url = $database_type.':'.'//'.$user.':'.$password.'@'.$hostname.':'.$port.'/'.$database; + + // XXX should use setURL and setTable + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @public + */ + function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ( $this->isInitialized() ) + return; + // call the ancestor's method (mark as initialized) + parent::init(); + + //include phpDB library (the test was introduced in release 0.4.8 for + //the integration into Tikiwiki). + if (!class_exists('DB')) { + include_once('DB.php'); + } + + // try to connect to the database + $this->_link = DB::connect($this->getURL()); + if ( DB::isError($this->_link) ) { + phpCAS::error('could not connect to database ('.DB::errorMessage($this->_link).')'); + } + var_dump($this->_link); + phpCAS::traceBEnd(); + } + + /** @} */ +} + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php index bc07485b8f..d48a60d670 100644 --- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php +++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-file.php @@ -1,249 +1,249 @@ - - * - * @ingroup internalPGTStorageFile - */ - -class PGTStorageFile extends PGTStorage -{ - /** - * @addtogroup internalPGTStorageFile - * @{ - */ - - /** - * a string telling where PGT's should be stored on the filesystem. Written by - * PGTStorageFile::PGTStorageFile(), read by getPath(). - * - * @private - */ - var $_path; - - /** - * This method returns the name of the directory where PGT's should be stored - * on the filesystem. - * - * @return the name of a directory (with leading and trailing '/') - * - * @private - */ - function getPath() - { - return $this->_path; - } - - /** - * a string telling the format to use to store PGT's (plain or xml). Written by - * PGTStorageFile::PGTStorageFile(), read by getFormat(). - * - * @private - */ - var $_format; - - /** - * This method returns the format to use when storing PGT's on the filesystem. - * - * @return a string corresponding to the format used (plain or xml). - * - * @private - */ - function getFormat() - { - return $this->_format; - } - - // ######################################################################## - // DEBUGGING - // ######################################################################## - - /** - * This method returns an informational string giving the type of storage - * used by the object (used for debugging purposes). - * - * @return an informational string. - * @public - */ - function getStorageType() - { - return "file"; - } - - /** - * This method returns an informational string giving informations on the - * parameters of the storage.(used for debugging purposes). - * - * @return an informational string. - * @public - */ - function getStorageInfo() - { - return 'path=`'.$this->getPath().'\', format=`'.$this->getFormat().'\''; - } - - // ######################################################################## - // CONSTRUCTOR - // ######################################################################## - - /** - * The class constructor, called by CASClient::SetPGTStorageFile(). - * - * @param $cas_parent the CASClient instance that creates the object. - * @param $format the format used to store the PGT's (`plain' and `xml' allowed). - * @param $path the path where the PGT's should be stored - * - * @public - */ - function PGTStorageFile($cas_parent,$format,$path) - { - phpCAS::traceBegin(); - // call the ancestor's constructor - $this->PGTStorage($cas_parent); - - if (empty($format) ) $format = CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT; - if (empty($path) ) $path = CAS_PGT_STORAGE_FILE_DEFAULT_PATH; - - // check that the path is an absolute path - if (getenv("OS")=="Windows_NT"){ - - if (!preg_match('`^[a-zA-Z]:`', $path)) { - phpCAS::error('an absolute path is needed for PGT storage to file'); - } - - } - else - { - - if ( $path[0] != '/' ) { - phpCAS::error('an absolute path is needed for PGT storage to file'); - } - - // store the path (with a leading and trailing '/') - $path = preg_replace('|[/]*$|','/',$path); - $path = preg_replace('|^[/]*|','/',$path); - } - - $this->_path = $path; - // check the format and store it - switch ($format) { - case CAS_PGT_STORAGE_FILE_FORMAT_PLAIN: - case CAS_PGT_STORAGE_FILE_FORMAT_XML: - $this->_format = $format; - break; - default: - phpCAS::error('unknown PGT file storage format (`'.CAS_PGT_STORAGE_FILE_FORMAT_PLAIN.'\' and `'.CAS_PGT_STORAGE_FILE_FORMAT_XML.'\' allowed)'); - } - phpCAS::traceEnd(); - } - - // ######################################################################## - // INITIALIZATION - // ######################################################################## - - /** - * This method is used to initialize the storage. Halts on error. - * - * @public - */ - function init() - { - phpCAS::traceBegin(); - // if the storage has already been initialized, return immediatly - if ( $this->isInitialized() ) - return; - // call the ancestor's method (mark as initialized) - parent::init(); - phpCAS::traceEnd(); - } - - // ######################################################################## - // PGT I/O - // ######################################################################## - - /** - * This method returns the filename corresponding to a PGT Iou. - * - * @param $pgt_iou the PGT iou. - * - * @return a filename - * @private - */ - function getPGTIouFilename($pgt_iou) - { - phpCAS::traceBegin(); - $filename = $this->getPath().$pgt_iou.'.'.$this->getFormat(); - phpCAS::traceEnd($filename); - return $filename; - } - - /** - * This method stores a PGT and its corresponding PGT Iou into a file. Echoes a - * warning on error. - * - * @param $pgt the PGT - * @param $pgt_iou the PGT iou - * - * @public - */ - function write($pgt,$pgt_iou) - { - phpCAS::traceBegin(); - $fname = $this->getPGTIouFilename($pgt_iou); - if ( $f=fopen($fname,"w") ) { - if ( fputs($f,$pgt) === FALSE ) { - phpCAS::error('could not write PGT to `'.$fname.'\''); - } - fclose($f); - } else { - phpCAS::error('could not open `'.$fname.'\''); - } - phpCAS::traceEnd(); - } - - /** - * This method reads a PGT corresponding to a PGT Iou and deletes the - * corresponding file. - * - * @param $pgt_iou the PGT iou - * - * @return the corresponding PGT, or FALSE on error - * - * @public - */ - function read($pgt_iou) - { - phpCAS::traceBegin(); - $pgt = FALSE; - $fname = $this->getPGTIouFilename($pgt_iou); - if ( !($f=fopen($fname,"r")) ) { - phpCAS::trace('could not open `'.$fname.'\''); - } else { - if ( ($pgt=fgets($f)) === FALSE ) { - phpCAS::trace('could not read PGT from `'.$fname.'\''); - } - fclose($f); - } - - // delete the PGT file - @unlink($fname); - - phpCAS::traceEnd($pgt); - return $pgt; - } - - /** @} */ - -} - - + + * + * @ingroup internalPGTStorageFile + */ + +class PGTStorageFile extends PGTStorage +{ + /** + * @addtogroup internalPGTStorageFile + * @{ + */ + + /** + * a string telling where PGT's should be stored on the filesystem. Written by + * PGTStorageFile::PGTStorageFile(), read by getPath(). + * + * @private + */ + var $_path; + + /** + * This method returns the name of the directory where PGT's should be stored + * on the filesystem. + * + * @return the name of a directory (with leading and trailing '/') + * + * @private + */ + function getPath() + { + return $this->_path; + } + + /** + * a string telling the format to use to store PGT's (plain or xml). Written by + * PGTStorageFile::PGTStorageFile(), read by getFormat(). + * + * @private + */ + var $_format; + + /** + * This method returns the format to use when storing PGT's on the filesystem. + * + * @return a string corresponding to the format used (plain or xml). + * + * @private + */ + function getFormat() + { + return $this->_format; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return an informational string. + * @public + */ + function getStorageType() + { + return "file"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return an informational string. + * @public + */ + function getStorageInfo() + { + return 'path=`'.$this->getPath().'\', format=`'.$this->getFormat().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor, called by CASClient::SetPGTStorageFile(). + * + * @param $cas_parent the CASClient instance that creates the object. + * @param $format the format used to store the PGT's (`plain' and `xml' allowed). + * @param $path the path where the PGT's should be stored + * + * @public + */ + function PGTStorageFile($cas_parent,$format,$path) + { + phpCAS::traceBegin(); + // call the ancestor's constructor + $this->PGTStorage($cas_parent); + + if (empty($format) ) $format = CAS_PGT_STORAGE_FILE_DEFAULT_FORMAT; + if (empty($path) ) $path = CAS_PGT_STORAGE_FILE_DEFAULT_PATH; + + // check that the path is an absolute path + if (getenv("OS")=="Windows_NT"){ + + if (!preg_match('`^[a-zA-Z]:`', $path)) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + } + else + { + + if ( $path[0] != '/' ) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + // store the path (with a leading and trailing '/') + $path = preg_replace('|[/]*$|','/',$path); + $path = preg_replace('|^[/]*|','/',$path); + } + + $this->_path = $path; + // check the format and store it + switch ($format) { + case CAS_PGT_STORAGE_FILE_FORMAT_PLAIN: + case CAS_PGT_STORAGE_FILE_FORMAT_XML: + $this->_format = $format; + break; + default: + phpCAS::error('unknown PGT file storage format (`'.CAS_PGT_STORAGE_FILE_FORMAT_PLAIN.'\' and `'.CAS_PGT_STORAGE_FILE_FORMAT_XML.'\' allowed)'); + } + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @public + */ + function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ( $this->isInitialized() ) + return; + // call the ancestor's method (mark as initialized) + parent::init(); + phpCAS::traceEnd(); + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method returns the filename corresponding to a PGT Iou. + * + * @param $pgt_iou the PGT iou. + * + * @return a filename + * @private + */ + function getPGTIouFilename($pgt_iou) + { + phpCAS::traceBegin(); + $filename = $this->getPath().$pgt_iou.'.'.$this->getFormat(); + phpCAS::traceEnd($filename); + return $filename; + } + + /** + * This method stores a PGT and its corresponding PGT Iou into a file. Echoes a + * warning on error. + * + * @param $pgt the PGT + * @param $pgt_iou the PGT iou + * + * @public + */ + function write($pgt,$pgt_iou) + { + phpCAS::traceBegin(); + $fname = $this->getPGTIouFilename($pgt_iou); + if ( $f=fopen($fname,"w") ) { + if ( fputs($f,$pgt) === FALSE ) { + phpCAS::error('could not write PGT to `'.$fname.'\''); + } + fclose($f); + } else { + phpCAS::error('could not open `'.$fname.'\''); + } + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding file. + * + * @param $pgt_iou the PGT iou + * + * @return the corresponding PGT, or FALSE on error + * + * @public + */ + function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = FALSE; + $fname = $this->getPGTIouFilename($pgt_iou); + if ( !($f=fopen($fname,"r")) ) { + phpCAS::trace('could not open `'.$fname.'\''); + } else { + if ( ($pgt=fgets($f)) === FALSE ) { + phpCAS::trace('could not read PGT from `'.$fname.'\''); + } + fclose($f); + } + + // delete the PGT file + @unlink($fname); + + phpCAS::traceEnd($pgt); + return $pgt; + } + + /** @} */ + +} + + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php index cd9b499671..8fd3c9e12b 100644 --- a/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php +++ b/plugins/CasAuthentication/extlib/CAS/PGTStorage/pgt-main.php @@ -1,188 +1,188 @@ - - * - * @ingroup internalPGTStorage - */ - -class PGTStorage -{ - /** - * @addtogroup internalPGTStorage - * @{ - */ - - // ######################################################################## - // CONSTRUCTOR - // ######################################################################## - - /** - * The constructor of the class, should be called only by inherited classes. - * - * @param $cas_parent the CASclient instance that creates the current object. - * - * @protected - */ - function PGTStorage($cas_parent) - { - phpCAS::traceBegin(); - if ( !$cas_parent->isProxy() ) { - phpCAS::error('defining PGT storage makes no sense when not using a CAS proxy'); - } - phpCAS::traceEnd(); - } - - // ######################################################################## - // DEBUGGING - // ######################################################################## - - /** - * This virtual method returns an informational string giving the type of storage - * used by the object (used for debugging purposes). - * - * @public - */ - function getStorageType() - { - phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); - } - - /** - * This virtual method returns an informational string giving informations on the - * parameters of the storage.(used for debugging purposes). - * - * @public - */ - function getStorageInfo() - { - phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); - } - - // ######################################################################## - // ERROR HANDLING - // ######################################################################## - - /** - * string used to store an error message. Written by PGTStorage::setErrorMessage(), - * read by PGTStorage::getErrorMessage(). - * - * @hideinitializer - * @private - * @deprecated not used. - */ - var $_error_message=FALSE; - - /** - * This method sets en error message, which can be read later by - * PGTStorage::getErrorMessage(). - * - * @param $error_message an error message - * - * @protected - * @deprecated not used. - */ - function setErrorMessage($error_message) - { - $this->_error_message = $error_message; - } - - /** - * This method returns an error message set by PGTStorage::setErrorMessage(). - * - * @return an error message when set by PGTStorage::setErrorMessage(), FALSE - * otherwise. - * - * @public - * @deprecated not used. - */ - function getErrorMessage() - { - return $this->_error_message; - } - - // ######################################################################## - // INITIALIZATION - // ######################################################################## - - /** - * a boolean telling if the storage has already been initialized. Written by - * PGTStorage::init(), read by PGTStorage::isInitialized(). - * - * @hideinitializer - * @private - */ - var $_initialized = FALSE; - - /** - * This method tells if the storage has already been intialized. - * - * @return a boolean - * - * @protected - */ - function isInitialized() - { - return $this->_initialized; - } - - /** - * This virtual method initializes the object. - * - * @protected - */ - function init() - { - $this->_initialized = TRUE; - } - - // ######################################################################## - // PGT I/O - // ######################################################################## - - /** - * This virtual method stores a PGT and its corresponding PGT Iuo. - * @note Should never be called. - * - * @param $pgt the PGT - * @param $pgt_iou the PGT iou - * - * @protected - */ - function write($pgt,$pgt_iou) - { - phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); - } - - /** - * This virtual method reads a PGT corresponding to a PGT Iou and deletes - * the corresponding storage entry. - * @note Should never be called. - * - * @param $pgt_iou the PGT iou - * - * @protected - */ - function read($pgt_iou) - { - phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); - } - - /** @} */ - -} - -// include specific PGT storage classes -include_once(dirname(__FILE__).'/pgt-file.php'); -include_once(dirname(__FILE__).'/pgt-db.php'); - + + * + * @ingroup internalPGTStorage + */ + +class PGTStorage +{ + /** + * @addtogroup internalPGTStorage + * @{ + */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The constructor of the class, should be called only by inherited classes. + * + * @param $cas_parent the CASclient instance that creates the current object. + * + * @protected + */ + function PGTStorage($cas_parent) + { + phpCAS::traceBegin(); + if ( !$cas_parent->isProxy() ) { + phpCAS::error('defining PGT storage makes no sense when not using a CAS proxy'); + } + phpCAS::traceEnd(); + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This virtual method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @public + */ + function getStorageType() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @public + */ + function getStorageInfo() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + // ######################################################################## + // ERROR HANDLING + // ######################################################################## + + /** + * string used to store an error message. Written by PGTStorage::setErrorMessage(), + * read by PGTStorage::getErrorMessage(). + * + * @hideinitializer + * @private + * @deprecated not used. + */ + var $_error_message=FALSE; + + /** + * This method sets en error message, which can be read later by + * PGTStorage::getErrorMessage(). + * + * @param $error_message an error message + * + * @protected + * @deprecated not used. + */ + function setErrorMessage($error_message) + { + $this->_error_message = $error_message; + } + + /** + * This method returns an error message set by PGTStorage::setErrorMessage(). + * + * @return an error message when set by PGTStorage::setErrorMessage(), FALSE + * otherwise. + * + * @public + * @deprecated not used. + */ + function getErrorMessage() + { + return $this->_error_message; + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * a boolean telling if the storage has already been initialized. Written by + * PGTStorage::init(), read by PGTStorage::isInitialized(). + * + * @hideinitializer + * @private + */ + var $_initialized = FALSE; + + /** + * This method tells if the storage has already been intialized. + * + * @return a boolean + * + * @protected + */ + function isInitialized() + { + return $this->_initialized; + } + + /** + * This virtual method initializes the object. + * + * @protected + */ + function init() + { + $this->_initialized = TRUE; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This virtual method stores a PGT and its corresponding PGT Iuo. + * @note Should never be called. + * + * @param $pgt the PGT + * @param $pgt_iou the PGT iou + * + * @protected + */ + function write($pgt,$pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method reads a PGT corresponding to a PGT Iou and deletes + * the corresponding storage entry. + * @note Should never be called. + * + * @param $pgt_iou the PGT iou + * + * @protected + */ + function read($pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** @} */ + +} + +// include specific PGT storage classes +include_once(dirname(__FILE__).'/pgt-file.php'); +include_once(dirname(__FILE__).'/pgt-db.php'); + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/client.php b/plugins/CasAuthentication/extlib/CAS/client.php index bfea59052d..bbde55a284 100644 --- a/plugins/CasAuthentication/extlib/CAS/client.php +++ b/plugins/CasAuthentication/extlib/CAS/client.php @@ -1,2297 +1,2297 @@ - - */ - -class CASClient -{ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX CONFIGURATION XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - // ######################################################################## - // HTML OUTPUT - // ######################################################################## - /** - * @addtogroup internalOutput - * @{ - */ - - /** - * This method filters a string by replacing special tokens by appropriate values - * and prints it. The corresponding tokens are taken into account: - * - __CAS_VERSION__ - * - __PHPCAS_VERSION__ - * - __SERVER_BASE_URL__ - * - * Used by CASClient::PrintHTMLHeader() and CASClient::printHTMLFooter(). - * - * @param $str the string to filter and output - * - * @private - */ - function HTMLFilterOutput($str) - { - $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str); - $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str); - $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str); - echo $str; - } - - /** - * A string used to print the header of HTML pages. Written by CASClient::setHTMLHeader(), - * read by CASClient::printHTMLHeader(). - * - * @hideinitializer - * @private - * @see CASClient::setHTMLHeader, CASClient::printHTMLHeader() - */ - var $_output_header = ''; - - /** - * This method prints the header of the HTML output (after filtering). If - * CASClient::setHTMLHeader() was not used, a default header is output. - * - * @param $title the title of the page - * - * @see HTMLFilterOutput() - * @private - */ - function printHTMLHeader($title) - { - $this->HTMLFilterOutput(str_replace('__TITLE__', - $title, - (empty($this->_output_header) - ? '__TITLE__

__TITLE__

' - : $this->_output_header) - ) - ); - } - - /** - * A string used to print the footer of HTML pages. Written by CASClient::setHTMLFooter(), - * read by printHTMLFooter(). - * - * @hideinitializer - * @private - * @see CASClient::setHTMLFooter, CASClient::printHTMLFooter() - */ - var $_output_footer = ''; - - /** - * This method prints the footer of the HTML output (after filtering). If - * CASClient::setHTMLFooter() was not used, a default footer is output. - * - * @see HTMLFilterOutput() - * @private - */ - function printHTMLFooter() - { - $this->HTMLFilterOutput(empty($this->_output_footer) - ?('
phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
') - :$this->_output_footer); - } - - /** - * This method set the HTML header used for all outputs. - * - * @param $header the HTML header. - * - * @public - */ - function setHTMLHeader($header) - { - $this->_output_header = $header; - } - - /** - * This method set the HTML footer used for all outputs. - * - * @param $footer the HTML footer. - * - * @public - */ - function setHTMLFooter($footer) - { - $this->_output_footer = $footer; - } - - /** @} */ - // ######################################################################## - // INTERNATIONALIZATION - // ######################################################################## - /** - * @addtogroup internalLang - * @{ - */ - /** - * A string corresponding to the language used by phpCAS. Written by - * CASClient::setLang(), read by CASClient::getLang(). - - * @note debugging information is always in english (debug purposes only). - * - * @hideinitializer - * @private - * @sa CASClient::_strings, CASClient::getString() - */ - var $_lang = ''; - - /** - * This method returns the language used by phpCAS. - * - * @return a string representing the language - * - * @private - */ - function getLang() - { - if ( empty($this->_lang) ) - $this->setLang(PHPCAS_LANG_DEFAULT); - return $this->_lang; - } - - /** - * array containing the strings used by phpCAS. Written by CASClient::setLang(), read by - * CASClient::getString() and used by CASClient::setLang(). - * - * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php - * - * @private - * @see CASClient::_lang, CASClient::getString(), CASClient::setLang(), CASClient::getLang() - */ - var $_strings; - - /** - * This method returns a string depending on the language. - * - * @param $str the index of the string in $_string. - * - * @return the string corresponding to $index in $string. - * - * @private - */ - function getString($str) - { - // call CASclient::getLang() to be sure the language is initialized - $this->getLang(); - - if ( !isset($this->_strings[$str]) ) { - trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR); - } - return $this->_strings[$str]; - } - - /** - * This method is used to set the language used by phpCAS. - * @note Can be called only once. - * - * @param $lang a string representing the language. - * - * @public - * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH - */ - function setLang($lang) - { - // include the corresponding language file - include_once(dirname(__FILE__).'/languages/'.$lang.'.php'); - - if ( !is_array($this->_strings) ) { - trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR); - } - $this->_lang = $lang; - } - - /** @} */ - // ######################################################################## - // CAS SERVER CONFIG - // ######################################################################## - /** - * @addtogroup internalConfig - * @{ - */ - - /** - * a record to store information about the CAS server. - * - $_server["version"]: the version of the CAS server - * - $_server["hostname"]: the hostname of the CAS server - * - $_server["port"]: the port the CAS server is running on - * - $_server["uri"]: the base URI the CAS server is responding on - * - $_server["base_url"]: the base URL of the CAS server - * - $_server["login_url"]: the login URL of the CAS server - * - $_server["service_validate_url"]: the service validating URL of the CAS server - * - $_server["proxy_url"]: the proxy URL of the CAS server - * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server - * - $_server["logout_url"]: the logout URL of the CAS server - * - * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"] - * are written by CASClient::CASClient(), read by CASClient::getServerVersion(), - * CASClient::getServerHostname(), CASClient::getServerPort() and CASClient::getServerURI(). - * - * The other fields are written and read by CASClient::getServerBaseURL(), - * CASClient::getServerLoginURL(), CASClient::getServerServiceValidateURL(), - * CASClient::getServerProxyValidateURL() and CASClient::getServerLogoutURL(). - * - * @hideinitializer - * @private - */ - var $_server = array( - 'version' => -1, - 'hostname' => 'none', - 'port' => -1, - 'uri' => 'none' - ); - - /** - * This method is used to retrieve the version of the CAS server. - * @return the version of the CAS server. - * @private - */ - function getServerVersion() - { - return $this->_server['version']; - } - - /** - * This method is used to retrieve the hostname of the CAS server. - * @return the hostname of the CAS server. - * @private - */ - function getServerHostname() - { return $this->_server['hostname']; } - - /** - * This method is used to retrieve the port of the CAS server. - * @return the port of the CAS server. - * @private - */ - function getServerPort() - { return $this->_server['port']; } - - /** - * This method is used to retrieve the URI of the CAS server. - * @return a URI. - * @private - */ - function getServerURI() - { return $this->_server['uri']; } - - /** - * This method is used to retrieve the base URL of the CAS server. - * @return a URL. - * @private - */ - function getServerBaseURL() - { - // the URL is build only when needed - if ( empty($this->_server['base_url']) ) { - $this->_server['base_url'] = 'https://' - .$this->getServerHostname() - .':' - .$this->getServerPort() - .$this->getServerURI(); - } - return $this->_server['base_url']; - } - - /** - * This method is used to retrieve the login URL of the CAS server. - * @param $gateway true to check authentication, false to force it - * @param $renew true to force the authentication with the CAS server - * NOTE : It is recommended that CAS implementations ignore the - "gateway" parameter if "renew" is set - * @return a URL. - * @private - */ - function getServerLoginURL($gateway=false,$renew=false) { - phpCAS::traceBegin(); - // the URL is build only when needed - if ( empty($this->_server['login_url']) ) { - $this->_server['login_url'] = $this->getServerBaseURL(); - $this->_server['login_url'] .= 'login?service='; - // $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL()); - $this->_server['login_url'] .= urlencode($this->getURL()); - if($renew) { - // It is recommended that when the "renew" parameter is set, its value be "true" - $this->_server['login_url'] .= '&renew=true'; - } elseif ($gateway) { - // It is recommended that when the "gateway" parameter is set, its value be "true" - $this->_server['login_url'] .= '&gateway=true'; - } - } - phpCAS::traceEnd($this->_server['login_url']); - return $this->_server['login_url']; - } - - /** - * This method sets the login URL of the CAS server. - * @param $url the login URL - * @private - * @since 0.4.21 by Wyman Chan - */ - function setServerLoginURL($url) - { - return $this->_server['login_url'] = $url; - } - - /** - * This method is used to retrieve the service validating URL of the CAS server. - * @return a URL. - * @private - */ - function getServerServiceValidateURL() - { - // the URL is build only when needed - if ( empty($this->_server['service_validate_url']) ) { - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: - $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate'; - break; - case CAS_VERSION_2_0: - $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate'; - break; - } - } - // return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); - return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL()); - } - - /** - * This method is used to retrieve the proxy validating URL of the CAS server. - * @return a URL. - * @private - */ - function getServerProxyValidateURL() - { - // the URL is build only when needed - if ( empty($this->_server['proxy_validate_url']) ) { - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: - $this->_server['proxy_validate_url'] = ''; - break; - case CAS_VERSION_2_0: - $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate'; - break; - } - } - // return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); - return $this->_server['proxy_validate_url'].'?service='.urlencode($this->getURL()); - } - - /** - * This method is used to retrieve the proxy URL of the CAS server. - * @return a URL. - * @private - */ - function getServerProxyURL() - { - // the URL is build only when needed - if ( empty($this->_server['proxy_url']) ) { - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: - $this->_server['proxy_url'] = ''; - break; - case CAS_VERSION_2_0: - $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy'; - break; - } - } - return $this->_server['proxy_url']; - } - - /** - * This method is used to retrieve the logout URL of the CAS server. - * @return a URL. - * @private - */ - function getServerLogoutURL() - { - // the URL is build only when needed - if ( empty($this->_server['logout_url']) ) { - $this->_server['logout_url'] = $this->getServerBaseURL().'logout'; - } - return $this->_server['logout_url']; - } - - /** - * This method sets the logout URL of the CAS server. - * @param $url the logout URL - * @private - * @since 0.4.21 by Wyman Chan - */ - function setServerLogoutURL($url) - { - return $this->_server['logout_url'] = $url; - } - - /** - * An array to store extra curl options. - */ - var $_curl_options = array(); - - /** - * This method is used to set additional user curl options. - */ - function setExtraCurlOption($key, $value) - { - $this->_curl_options[$key] = $value; - } - - /** - * This method checks to see if the request is secured via HTTPS - * @return true if https, false otherwise - * @private - */ - function isHttps() { - //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) { - //0.4.24 by Hinnack - if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { - return true; - } else { - return false; - } - } - - // ######################################################################## - // CONSTRUCTOR - // ######################################################################## - /** - * CASClient constructor. - * - * @param $server_version the version of the CAS server - * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise - * @param $server_hostname the hostname of the CAS server - * @param $server_port the port the CAS server is running on - * @param $server_uri the URI the CAS server is responding on - * @param $start_session Have phpCAS start PHP sessions (default true) - * - * @return a newly created CASClient object - * - * @public - */ - function CASClient( - $server_version, - $proxy, - $server_hostname, - $server_port, - $server_uri, - $start_session = true) { - - phpCAS::traceBegin(); - - if (!$this->isLogoutRequest() && !empty($_GET['ticket']) && $start_session) { - // copy old session vars and destroy the current session - if (!isset($_SESSION)) { - session_start(); - } - $old_session = $_SESSION; - session_destroy(); - // set up a new session, of name based on the ticket - $session_id = preg_replace('/[^\w]/','',$_GET['ticket']); - phpCAS::LOG("Session ID: " . $session_id); - session_id($session_id); - if (!isset($_SESSION)) { - session_start(); - } - // restore old session vars + + */ + +class CASClient +{ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX CONFIGURATION XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup internalOutput + * @{ + */ + + /** + * This method filters a string by replacing special tokens by appropriate values + * and prints it. The corresponding tokens are taken into account: + * - __CAS_VERSION__ + * - __PHPCAS_VERSION__ + * - __SERVER_BASE_URL__ + * + * Used by CASClient::PrintHTMLHeader() and CASClient::printHTMLFooter(). + * + * @param $str the string to filter and output + * + * @private + */ + function HTMLFilterOutput($str) + { + $str = str_replace('__CAS_VERSION__',$this->getServerVersion(),$str); + $str = str_replace('__PHPCAS_VERSION__',phpCAS::getVersion(),$str); + $str = str_replace('__SERVER_BASE_URL__',$this->getServerBaseURL(),$str); + echo $str; + } + + /** + * A string used to print the header of HTML pages. Written by CASClient::setHTMLHeader(), + * read by CASClient::printHTMLHeader(). + * + * @hideinitializer + * @private + * @see CASClient::setHTMLHeader, CASClient::printHTMLHeader() + */ + var $_output_header = ''; + + /** + * This method prints the header of the HTML output (after filtering). If + * CASClient::setHTMLHeader() was not used, a default header is output. + * + * @param $title the title of the page + * + * @see HTMLFilterOutput() + * @private + */ + function printHTMLHeader($title) + { + $this->HTMLFilterOutput(str_replace('__TITLE__', + $title, + (empty($this->_output_header) + ? '__TITLE__

__TITLE__

' + : $this->_output_header) + ) + ); + } + + /** + * A string used to print the footer of HTML pages. Written by CASClient::setHTMLFooter(), + * read by printHTMLFooter(). + * + * @hideinitializer + * @private + * @see CASClient::setHTMLFooter, CASClient::printHTMLFooter() + */ + var $_output_footer = ''; + + /** + * This method prints the footer of the HTML output (after filtering). If + * CASClient::setHTMLFooter() was not used, a default footer is output. + * + * @see HTMLFilterOutput() + * @private + */ + function printHTMLFooter() + { + $this->HTMLFilterOutput(empty($this->_output_footer) + ?('
phpCAS __PHPCAS_VERSION__ '.$this->getString(CAS_STR_USING_SERVER).' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
') + :$this->_output_footer); + } + + /** + * This method set the HTML header used for all outputs. + * + * @param $header the HTML header. + * + * @public + */ + function setHTMLHeader($header) + { + $this->_output_header = $header; + } + + /** + * This method set the HTML footer used for all outputs. + * + * @param $footer the HTML footer. + * + * @public + */ + function setHTMLFooter($footer) + { + $this->_output_footer = $footer; + } + + /** @} */ + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup internalLang + * @{ + */ + /** + * A string corresponding to the language used by phpCAS. Written by + * CASClient::setLang(), read by CASClient::getLang(). + + * @note debugging information is always in english (debug purposes only). + * + * @hideinitializer + * @private + * @sa CASClient::_strings, CASClient::getString() + */ + var $_lang = ''; + + /** + * This method returns the language used by phpCAS. + * + * @return a string representing the language + * + * @private + */ + function getLang() + { + if ( empty($this->_lang) ) + $this->setLang(PHPCAS_LANG_DEFAULT); + return $this->_lang; + } + + /** + * array containing the strings used by phpCAS. Written by CASClient::setLang(), read by + * CASClient::getString() and used by CASClient::setLang(). + * + * @note This array is filled by instructions in CAS/languages/<$this->_lang>.php + * + * @private + * @see CASClient::_lang, CASClient::getString(), CASClient::setLang(), CASClient::getLang() + */ + var $_strings; + + /** + * This method returns a string depending on the language. + * + * @param $str the index of the string in $_string. + * + * @return the string corresponding to $index in $string. + * + * @private + */ + function getString($str) + { + // call CASclient::getLang() to be sure the language is initialized + $this->getLang(); + + if ( !isset($this->_strings[$str]) ) { + trigger_error('string `'.$str.'\' not defined for language `'.$this->getLang().'\'',E_USER_ERROR); + } + return $this->_strings[$str]; + } + + /** + * This method is used to set the language used by phpCAS. + * @note Can be called only once. + * + * @param $lang a string representing the language. + * + * @public + * @sa CAS_LANG_FRENCH, CAS_LANG_ENGLISH + */ + function setLang($lang) + { + // include the corresponding language file + include_once(dirname(__FILE__).'/languages/'.$lang.'.php'); + + if ( !is_array($this->_strings) ) { + trigger_error('language `'.$lang.'\' is not implemented',E_USER_ERROR); + } + $this->_lang = $lang; + } + + /** @} */ + // ######################################################################## + // CAS SERVER CONFIG + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * a record to store information about the CAS server. + * - $_server["version"]: the version of the CAS server + * - $_server["hostname"]: the hostname of the CAS server + * - $_server["port"]: the port the CAS server is running on + * - $_server["uri"]: the base URI the CAS server is responding on + * - $_server["base_url"]: the base URL of the CAS server + * - $_server["login_url"]: the login URL of the CAS server + * - $_server["service_validate_url"]: the service validating URL of the CAS server + * - $_server["proxy_url"]: the proxy URL of the CAS server + * - $_server["proxy_validate_url"]: the proxy validating URL of the CAS server + * - $_server["logout_url"]: the logout URL of the CAS server + * + * $_server["version"], $_server["hostname"], $_server["port"] and $_server["uri"] + * are written by CASClient::CASClient(), read by CASClient::getServerVersion(), + * CASClient::getServerHostname(), CASClient::getServerPort() and CASClient::getServerURI(). + * + * The other fields are written and read by CASClient::getServerBaseURL(), + * CASClient::getServerLoginURL(), CASClient::getServerServiceValidateURL(), + * CASClient::getServerProxyValidateURL() and CASClient::getServerLogoutURL(). + * + * @hideinitializer + * @private + */ + var $_server = array( + 'version' => -1, + 'hostname' => 'none', + 'port' => -1, + 'uri' => 'none' + ); + + /** + * This method is used to retrieve the version of the CAS server. + * @return the version of the CAS server. + * @private + */ + function getServerVersion() + { + return $this->_server['version']; + } + + /** + * This method is used to retrieve the hostname of the CAS server. + * @return the hostname of the CAS server. + * @private + */ + function getServerHostname() + { return $this->_server['hostname']; } + + /** + * This method is used to retrieve the port of the CAS server. + * @return the port of the CAS server. + * @private + */ + function getServerPort() + { return $this->_server['port']; } + + /** + * This method is used to retrieve the URI of the CAS server. + * @return a URI. + * @private + */ + function getServerURI() + { return $this->_server['uri']; } + + /** + * This method is used to retrieve the base URL of the CAS server. + * @return a URL. + * @private + */ + function getServerBaseURL() + { + // the URL is build only when needed + if ( empty($this->_server['base_url']) ) { + $this->_server['base_url'] = 'https://' + .$this->getServerHostname() + .':' + .$this->getServerPort() + .$this->getServerURI(); + } + return $this->_server['base_url']; + } + + /** + * This method is used to retrieve the login URL of the CAS server. + * @param $gateway true to check authentication, false to force it + * @param $renew true to force the authentication with the CAS server + * NOTE : It is recommended that CAS implementations ignore the + "gateway" parameter if "renew" is set + * @return a URL. + * @private + */ + function getServerLoginURL($gateway=false,$renew=false) { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['login_url']) ) { + $this->_server['login_url'] = $this->getServerBaseURL(); + $this->_server['login_url'] .= 'login?service='; + // $this->_server['login_url'] .= preg_replace('/&/','%26',$this->getURL()); + $this->_server['login_url'] .= urlencode($this->getURL()); + if($renew) { + // It is recommended that when the "renew" parameter is set, its value be "true" + $this->_server['login_url'] .= '&renew=true'; + } elseif ($gateway) { + // It is recommended that when the "gateway" parameter is set, its value be "true" + $this->_server['login_url'] .= '&gateway=true'; + } + } + phpCAS::traceEnd($this->_server['login_url']); + return $this->_server['login_url']; + } + + /** + * This method sets the login URL of the CAS server. + * @param $url the login URL + * @private + * @since 0.4.21 by Wyman Chan + */ + function setServerLoginURL($url) + { + return $this->_server['login_url'] = $url; + } + + /** + * This method is used to retrieve the service validating URL of the CAS server. + * @return a URL. + * @private + */ + function getServerServiceValidateURL() + { + // the URL is build only when needed + if ( empty($this->_server['service_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['service_validate_url'] = $this->getServerBaseURL().'validate'; + break; + case CAS_VERSION_2_0: + $this->_server['service_validate_url'] = $this->getServerBaseURL().'serviceValidate'; + break; + } + } + // return $this->_server['service_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); + return $this->_server['service_validate_url'].'?service='.urlencode($this->getURL()); + } + + /** + * This method is used to retrieve the proxy validating URL of the CAS server. + * @return a URL. + * @private + */ + function getServerProxyValidateURL() + { + // the URL is build only when needed + if ( empty($this->_server['proxy_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_validate_url'] = ''; + break; + case CAS_VERSION_2_0: + $this->_server['proxy_validate_url'] = $this->getServerBaseURL().'proxyValidate'; + break; + } + } + // return $this->_server['proxy_validate_url'].'?service='.preg_replace('/&/','%26',$this->getURL()); + return $this->_server['proxy_validate_url'].'?service='.urlencode($this->getURL()); + } + + /** + * This method is used to retrieve the proxy URL of the CAS server. + * @return a URL. + * @private + */ + function getServerProxyURL() + { + // the URL is build only when needed + if ( empty($this->_server['proxy_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_url'] = ''; + break; + case CAS_VERSION_2_0: + $this->_server['proxy_url'] = $this->getServerBaseURL().'proxy'; + break; + } + } + return $this->_server['proxy_url']; + } + + /** + * This method is used to retrieve the logout URL of the CAS server. + * @return a URL. + * @private + */ + function getServerLogoutURL() + { + // the URL is build only when needed + if ( empty($this->_server['logout_url']) ) { + $this->_server['logout_url'] = $this->getServerBaseURL().'logout'; + } + return $this->_server['logout_url']; + } + + /** + * This method sets the logout URL of the CAS server. + * @param $url the logout URL + * @private + * @since 0.4.21 by Wyman Chan + */ + function setServerLogoutURL($url) + { + return $this->_server['logout_url'] = $url; + } + + /** + * An array to store extra curl options. + */ + var $_curl_options = array(); + + /** + * This method is used to set additional user curl options. + */ + function setExtraCurlOption($key, $value) + { + $this->_curl_options[$key] = $value; + } + + /** + * This method checks to see if the request is secured via HTTPS + * @return true if https, false otherwise + * @private + */ + function isHttps() { + //if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) ) { + //0.4.24 by Hinnack + if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + return true; + } else { + return false; + } + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + /** + * CASClient constructor. + * + * @param $server_version the version of the CAS server + * @param $proxy TRUE if the CAS client is a CAS proxy, FALSE otherwise + * @param $server_hostname the hostname of the CAS server + * @param $server_port the port the CAS server is running on + * @param $server_uri the URI the CAS server is responding on + * @param $start_session Have phpCAS start PHP sessions (default true) + * + * @return a newly created CASClient object + * + * @public + */ + function CASClient( + $server_version, + $proxy, + $server_hostname, + $server_port, + $server_uri, + $start_session = true) { + + phpCAS::traceBegin(); + + if (!$this->isLogoutRequest() && !empty($_GET['ticket']) && $start_session) { + // copy old session vars and destroy the current session + if (!isset($_SESSION)) { + session_start(); + } + $old_session = $_SESSION; + session_destroy(); + // set up a new session, of name based on the ticket + $session_id = preg_replace('/[^\w]/','',$_GET['ticket']); + phpCAS::LOG("Session ID: " . $session_id); + session_id($session_id); + if (!isset($_SESSION)) { + session_start(); + } + // restore old session vars $_SESSION = $old_session; // Redirect to location without ticket. - header('Location: '.$this->getURL()); - } - - //activate session mechanism if desired - if (!$this->isLogoutRequest() && $start_session) { - session_start(); - } - - $this->_proxy = $proxy; - - //check version - switch ($server_version) { - case CAS_VERSION_1_0: - if ( $this->isProxy() ) - phpCAS::error('CAS proxies are not supported in CAS ' - .$server_version); - break; - case CAS_VERSION_2_0: - break; - default: - phpCAS::error('this version of CAS (`' - .$server_version - .'\') is not supported by phpCAS ' - .phpCAS::getVersion()); - } - $this->_server['version'] = $server_version; - - //check hostname - if ( empty($server_hostname) - || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) { - phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')'); - } - $this->_server['hostname'] = $server_hostname; - - //check port - if ( $server_port == 0 - || !is_int($server_port) ) { - phpCAS::error('bad CAS server port (`'.$server_hostname.'\')'); - } - $this->_server['port'] = $server_port; - - //check URI - if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) { - phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); - } - //add leading and trailing `/' and remove doubles - $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/'); - $this->_server['uri'] = $server_uri; - - //set to callback mode if PgtIou and PgtId CGI GET parameters are provided - if ( $this->isProxy() ) { - $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])); - } - - if ( $this->isCallbackMode() ) { - //callback mode: check that phpCAS is secured - if ( !$this->isHttps() ) { - phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'); - } - } else { - //normal mode: get ticket and remove it from CGI parameters for developpers - $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null); - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: // check for a Service Ticket - if( preg_match('/^ST-/',$ticket) ) { - phpCAS::trace('ST \''.$ticket.'\' found'); - //ST present - $this->setST($ticket); - //ticket has been taken into account, unset it to hide it to applications - unset($_GET['ticket']); - } else if ( !empty($ticket) ) { - //ill-formed ticket, halt - phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')'); - } - break; - case CAS_VERSION_2_0: // check for a Service or Proxy Ticket - if( preg_match('/^[SP]T-/',$ticket) ) { - phpCAS::trace('ST or PT \''.$ticket.'\' found'); - $this->setPT($ticket); - unset($_GET['ticket']); - } else if ( !empty($ticket) ) { - //ill-formed ticket, halt - phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')'); - } - break; - } - } - phpCAS::traceEnd(); - } - - /** @} */ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX AUTHENTICATION XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - /** - * @addtogroup internalAuthentication - * @{ - */ - - /** - * The Authenticated user. Written by CASClient::setUser(), read by CASClient::getUser(). - * @attention client applications should use phpCAS::getUser(). - * - * @hideinitializer - * @private - */ - var $_user = ''; - - /** - * This method sets the CAS user's login name. - * - * @param $user the login name of the authenticated user. - * - * @private - */ - function setUser($user) - { - $this->_user = $user; - } - - /** - * This method returns the CAS user's login name. - * @warning should be called only after CASClient::forceAuthentication() or - * CASClient::isAuthenticated(), otherwise halt with an error. - * - * @return the login name of the authenticated user - */ - function getUser() - { - if ( empty($this->_user) ) { - phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'); - } - return $this->_user; - } - - /** - * This method is called to renew the authentication of the user - * If the user is authenticated, renew the connection - * If not, redirect to CAS - * @public - */ - function renewAuthentication(){ - phpCAS::traceBegin(); - // Either way, the user is authenticated by CAS - if( isset( $_SESSION['phpCAS']['auth_checked'] ) ) - unset($_SESSION['phpCAS']['auth_checked']); - if ( $this->isAuthenticated() ) { - phpCAS::trace('user already authenticated; renew'); - $this->redirectToCas(false,true); - } else { - $this->redirectToCas(); - } - phpCAS::traceEnd(); - } - - /** - * This method is called to be sure that the user is authenticated. When not - * authenticated, halt by redirecting to the CAS server; otherwise return TRUE. - * @return TRUE when the user is authenticated; otherwise halt. - * @public - */ - function forceAuthentication() - { - phpCAS::traceBegin(); - - if ( $this->isAuthenticated() ) { - // the user is authenticated, nothing to be done. - phpCAS::trace('no need to authenticate'); - $res = TRUE; - } else { - // the user is not authenticated, redirect to the CAS server - if (isset($_SESSION['phpCAS']['auth_checked'])) { - unset($_SESSION['phpCAS']['auth_checked']); - } - $this->redirectToCas(FALSE/* no gateway */); - // never reached - $res = FALSE; - } - phpCAS::traceEnd($res); - return $res; - } - - /** - * An integer that gives the number of times authentication will be cached before rechecked. - * - * @hideinitializer - * @private - */ - var $_cache_times_for_auth_recheck = 0; - - /** - * Set the number of times authentication will be cached before rechecked. - * - * @param $n an integer. - * - * @public - */ - function setCacheTimesForAuthRecheck($n) - { - $this->_cache_times_for_auth_recheck = $n; - } - - /** - * This method is called to check whether the user is authenticated or not. - * @return TRUE when the user is authenticated, FALSE otherwise. - * @public - */ - function checkAuthentication() - { - phpCAS::traceBegin(); - - if ( $this->isAuthenticated() ) { - phpCAS::trace('user is authenticated'); - $res = TRUE; - } else if (isset($_SESSION['phpCAS']['auth_checked'])) { - // the previous request has redirected the client to the CAS server with gateway=true - unset($_SESSION['phpCAS']['auth_checked']); - $res = FALSE; - } else { - // $_SESSION['phpCAS']['auth_checked'] = true; - // $this->redirectToCas(TRUE/* gateway */); - // // never reached - // $res = FALSE; - // avoid a check against CAS on every request - if (! isset($_SESSION['phpCAS']['unauth_count']) ) - $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized - - if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1) - || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)) - { - $res = FALSE; - - if ($this->_cache_times_for_auth_recheck != -1) - { - $_SESSION['phpCAS']['unauth_count']++; - phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')'); - } - else - { - phpCAS::trace('user is not authenticated (cached for until login pressed)'); - } - } - else - { - $_SESSION['phpCAS']['unauth_count'] = 0; - $_SESSION['phpCAS']['auth_checked'] = true; - phpCAS::trace('user is not authenticated (cache reset)'); - $this->redirectToCas(TRUE/* gateway */); - // never reached - $res = FALSE; - } - } - phpCAS::traceEnd($res); - return $res; - } - - /** - * This method is called to check if the user is authenticated (previously or by - * tickets given in the URL). - * - * @return TRUE when the user is authenticated. - * - * @public - */ - function isAuthenticated() - { - phpCAS::traceBegin(); - $res = FALSE; - $validate_url = ''; - - if ( $this->wasPreviouslyAuthenticated() ) { - // the user has already (previously during the session) been - // authenticated, nothing to be done. - phpCAS::trace('user was already authenticated, no need to look for tickets'); - $res = TRUE; - } - elseif ( $this->hasST() ) { - // if a Service Ticket was given, validate it - phpCAS::trace('ST `'.$this->getST().'\' is present'); - $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts - phpCAS::trace('ST `'.$this->getST().'\' was validated'); - if ( $this->isProxy() ) { - $this->validatePGT($validate_url,$text_response,$tree_response); // idem - phpCAS::trace('PGT `'.$this->getPGT().'\' was validated'); - $_SESSION['phpCAS']['pgt'] = $this->getPGT(); - } - $_SESSION['phpCAS']['user'] = $this->getUser(); - $res = TRUE; - } - elseif ( $this->hasPT() ) { - // if a Proxy Ticket was given, validate it - phpCAS::trace('PT `'.$this->getPT().'\' is present'); - $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts - phpCAS::trace('PT `'.$this->getPT().'\' was validated'); - if ( $this->isProxy() ) { - $this->validatePGT($validate_url,$text_response,$tree_response); // idem - phpCAS::trace('PGT `'.$this->getPGT().'\' was validated'); - $_SESSION['phpCAS']['pgt'] = $this->getPGT(); - } - $_SESSION['phpCAS']['user'] = $this->getUser(); - $res = TRUE; - } - else { - // no ticket given, not authenticated - phpCAS::trace('no ticket found'); - } - - phpCAS::traceEnd($res); - return $res; - } - - /** - * This method tells if the current session is authenticated. - * @return true if authenticated based soley on $_SESSION variable - * @since 0.4.22 by Brendan Arnold - */ - function isSessionAuthenticated () - { - return !empty($_SESSION['phpCAS']['user']); - } - - /** - * This method tells if the user has already been (previously) authenticated - * by looking into the session variables. - * - * @note This function switches to callback mode when needed. - * - * @return TRUE when the user has already been authenticated; FALSE otherwise. - * - * @private - */ - function wasPreviouslyAuthenticated() - { - phpCAS::traceBegin(); - - if ( $this->isCallbackMode() ) { - $this->callback(); - } - - $auth = FALSE; - - if ( $this->isProxy() ) { - // CAS proxy: username and PGT must be present - if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { - // authentication already done - $this->setUser($_SESSION['phpCAS']['user']); - $this->setPGT($_SESSION['phpCAS']['pgt']); - phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\''); - $auth = TRUE; - } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) { - // these two variables should be empty or not empty at the same time - phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty'); - // unset all tickets to enforce authentication - unset($_SESSION['phpCAS']); - $this->setST(''); - $this->setPT(''); - } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { - // these two variables should be empty or not empty at the same time - phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty'); - // unset all tickets to enforce authentication - unset($_SESSION['phpCAS']); - $this->setST(''); - $this->setPT(''); - } else { - phpCAS::trace('neither user not PGT found'); - } - } else { - // `simple' CAS client (not a proxy): username must be present - if ( $this->isSessionAuthenticated() ) { - // authentication already done - $this->setUser($_SESSION['phpCAS']['user']); - phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\''); - $auth = TRUE; - } else { - phpCAS::trace('no user found'); - } - } - - phpCAS::traceEnd($auth); - return $auth; - } - - /** - * This method is used to redirect the client to the CAS server. - * It is used by CASClient::forceAuthentication() and CASClient::checkAuthentication(). - * @param $gateway true to check authentication, false to force it - * @param $renew true to force the authentication with the CAS server - * @public - */ - function redirectToCas($gateway=false,$renew=false){ - phpCAS::traceBegin(); - $cas_url = $this->getServerLoginURL($gateway,$renew); - header('Location: '.$cas_url); - phpCAS::log( "Redirect to : ".$cas_url ); - - $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED)); - - printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); - $this->printHTMLFooter(); - phpCAS::traceExit(); - exit(); - } - -// /** -// * This method is used to logout from CAS. -// * @param $url a URL that will be transmitted to the CAS server (to come back to when logged out) -// * @public -// */ -// function logout($url = "") { -// phpCAS::traceBegin(); -// $cas_url = $this->getServerLogoutURL(); -// // v0.4.14 sebastien.gougeon at univ-rennes1.fr -// // header('Location: '.$cas_url); -// if ( $url != "" ) { -// // Adam Moore 1.0.0RC2 -// $url = '?service=' . $url . '&url=' . $url; -// } -// header('Location: '.$cas_url . $url); -// session_unset(); -// session_destroy(); -// $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT)); -// printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); -// $this->printHTMLFooter(); -// phpCAS::traceExit(); -// exit(); -// } - - /** - * This method is used to logout from CAS. - * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server - * @public - */ - function logout($params) { - phpCAS::traceBegin(); - $cas_url = $this->getServerLogoutURL(); - $paramSeparator = '?'; - if (isset($params['url'])) { - $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']); - $paramSeparator = '&'; - } - if (isset($params['service'])) { - $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']); - } - header('Location: '.$cas_url); - session_unset(); - session_destroy(); - $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT)); - printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); - $this->printHTMLFooter(); - phpCAS::traceExit(); - exit(); - } - - /** - * @return true if the current request is a logout request. - * @private - */ - function isLogoutRequest() { - return !empty($_POST['logoutRequest']); - } - - /** - * @return true if a logout request is allowed. - * @private - */ - function isLogoutRequestAllowed() { - } - - /** - * This method handles logout requests. - * @param $check_client true to check the client bofore handling the request, - * false not to perform any access control. True by default. - * @param $allowed_clients an array of host names allowed to send logout requests. - * By default, only the CAs server (declared in the constructor) will be allowed. - * @public - */ - function handleLogoutRequests($check_client=true, $allowed_clients=false) { - phpCAS::traceBegin(); - if (!$this->isLogoutRequest()) { - phpCAS::log("Not a logout request"); - phpCAS::traceEnd(); - return; - } - phpCAS::log("Logout requested"); - phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']); - if ($check_client) { - if (!$allowed_clients) { - $allowed_clients = array( $this->getServerHostname() ); - } - $client_ip = $_SERVER['REMOTE_ADDR']; - $client = gethostbyaddr($client_ip); - phpCAS::log("Client: ".$client); - $allowed = false; - foreach ($allowed_clients as $allowed_client) { - if ($client == $allowed_client) { - phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed"); - $allowed = true; - break; - } else { - phpCAS::log("Allowed client '".$allowed_client."' does not match"); - } - } - if (!$allowed) { - phpCAS::error("Unauthorized logout request from client '".$client."'"); - printf("Unauthorized!"); - phpCAS::traceExit(); - exit(); - } - } else { - phpCAS::log("No access control set"); - } - // Extract the ticket from the SAML Request - preg_match("|(.*)|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3); - $wrappedSamlSessionIndex = preg_replace('||','',$tick[0][0]); - $ticket2logout = preg_replace('||','',$wrappedSamlSessionIndex); - phpCAS::log("Ticket to logout: ".$ticket2logout); - $session_id = preg_replace('/[^\w]/','',$ticket2logout); - phpCAS::log("Session id: ".$session_id); - - // fix New session ID - session_id($session_id); - $_COOKIE[session_name()]=$session_id; - $_GET[session_name()]=$session_id; - - // Overwrite session - session_start(); - session_unset(); - session_destroy(); - printf("Disconnected!"); - phpCAS::traceExit(); - exit(); - } - - /** @} */ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX BASIC CLIENT FEATURES (CAS 1.0) XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - // ######################################################################## - // ST - // ######################################################################## - /** - * @addtogroup internalBasic - * @{ - */ - - /** - * the Service Ticket provided in the URL of the request if present - * (empty otherwise). Written by CASClient::CASClient(), read by - * CASClient::getST() and CASClient::hasPGT(). - * - * @hideinitializer - * @private - */ - var $_st = ''; - - /** - * This method returns the Service Ticket provided in the URL of the request. - * @return The service ticket. - * @private - */ - function getST() - { return $this->_st; } - - /** - * This method stores the Service Ticket. - * @param $st The Service Ticket. - * @private - */ - function setST($st) - { $this->_st = $st; } - - /** - * This method tells if a Service Ticket was stored. - * @return TRUE if a Service Ticket has been stored. - * @private - */ - function hasST() - { return !empty($this->_st); } - - /** @} */ - - // ######################################################################## - // ST VALIDATION - // ######################################################################## - /** - * @addtogroup internalBasic - * @{ - */ - - /** - * the certificate of the CAS server. - * - * @hideinitializer - * @private - */ - var $_cas_server_cert = ''; - - /** - * the certificate of the CAS server CA. - * - * @hideinitializer - * @private - */ - var $_cas_server_ca_cert = ''; - - /** - * Set to true not to validate the CAS server. - * - * @hideinitializer - * @private - */ - var $_no_cas_server_validation = false; - - /** - * Set the certificate of the CAS server. - * - * @param $cert the PEM certificate - */ - function setCasServerCert($cert) - { - $this->_cas_server_cert = $cert; - } - - /** - * Set the CA certificate of the CAS server. - * - * @param $cert the PEM certificate of the CA that emited the cert of the server - */ - function setCasServerCACert($cert) - { - $this->_cas_server_ca_cert = $cert; - } - - /** - * Set no SSL validation for the CAS server. - */ - function setNoCasServerValidation() - { - $this->_no_cas_server_validation = true; - } - - /** - * This method is used to validate a ST; halt on failure, and sets $validate_url, - * $text_reponse and $tree_response on success. These parameters are used later - * by CASClient::validatePGT() for CAS proxies. - * - * @param $validate_url the URL of the request to the CAS server. - * @param $text_response the response of the CAS server, as is (XML text). - * @param $tree_response the response of the CAS server, as a DOM XML tree. - * - * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). - * - * @private - */ - function validateST($validate_url,&$text_response,&$tree_response) - { - phpCAS::traceBegin(); - // build the URL to validate the ticket - $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST(); - if ( $this->isProxy() ) { - // pass the callback url for CAS proxies - $validate_url .= '&pgtUrl='.$this->getCallbackURL(); - } - - // open and read the URL - if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) { - phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); - $this->authError('ST not validated', - $validate_url, - TRUE/*$no_response*/); - } - - // analyze the result depending on the version - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: - if (preg_match('/^no\n/',$text_response)) { - phpCAS::trace('ST has not been validated'); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - FALSE/*$bad_response*/, - $text_response); - } - if (!preg_match('/^yes\n/',$text_response)) { - phpCAS::trace('ill-formed response'); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - // ST has been validated, extract the user name - $arr = preg_split('/\n/',$text_response); - $this->setUser(trim($arr[1])); - break; - case CAS_VERSION_2_0: - // read the response of the CAS server into a DOM object - if ( !($dom = domxml_open_mem($text_response))) { - phpCAS::trace('domxml_open_mem() failed'); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - // read the root node of the XML tree - if ( !($tree_response = $dom->document_element()) ) { - phpCAS::trace('document_element() failed'); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - // insure that tag name is 'serviceResponse' - if ( $tree_response->node_name() != 'serviceResponse' ) { - phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name().'\''); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) { - // authentication succeded, extract the user name - if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) { - phpCAS::trace(' found, but no '); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - $user = trim($user_elements[0]->get_content()); - phpCAS::trace('user = `'.$user); - $this->setUser($user); - - } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) { - phpCAS::trace(' found'); - // authentication failed, extract the error code and message - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - FALSE/*$bad_response*/, - $text_response, - $failure_elements[0]->get_attribute('code')/*$err_code*/, - trim($failure_elements[0]->get_content())/*$err_msg*/); - } else { - phpCAS::trace('neither nor found'); - $this->authError('ST not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - break; - } - - // at this step, ST has been validated and $this->_user has been set, - phpCAS::traceEnd(TRUE); - return TRUE; - } - - /** @} */ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX PROXY FEATURES (CAS 2.0) XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - // ######################################################################## - // PROXYING - // ######################################################################## - /** - * @addtogroup internalProxy - * @{ - */ - - /** - * A boolean telling if the client is a CAS proxy or not. Written by CASClient::CASClient(), - * read by CASClient::isProxy(). - * - * @private - */ - var $_proxy; - - /** - * Tells if a CAS client is a CAS proxy or not - * - * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise - * - * @private - */ - function isProxy() - { - return $this->_proxy; - } - - /** @} */ - // ######################################################################## - // PGT - // ######################################################################## - /** - * @addtogroup internalProxy - * @{ - */ - - /** - * the Proxy Grnting Ticket given by the CAS server (empty otherwise). - * Written by CASClient::setPGT(), read by CASClient::getPGT() and CASClient::hasPGT(). - * - * @hideinitializer - * @private - */ - var $_pgt = ''; - - /** - * This method returns the Proxy Granting Ticket given by the CAS server. - * @return The Proxy Granting Ticket. - * @private - */ - function getPGT() - { return $this->_pgt; } - - /** - * This method stores the Proxy Granting Ticket. - * @param $pgt The Proxy Granting Ticket. - * @private - */ - function setPGT($pgt) - { $this->_pgt = $pgt; } - - /** - * This method tells if a Proxy Granting Ticket was stored. - * @return TRUE if a Proxy Granting Ticket has been stored. - * @private - */ - function hasPGT() - { return !empty($this->_pgt); } - - /** @} */ - - // ######################################################################## - // CALLBACK MODE - // ######################################################################## - /** - * @addtogroup internalCallback - * @{ - */ - /** - * each PHP script using phpCAS in proxy mode is its own callback to get the - * PGT back from the CAS server. callback_mode is detected by the constructor - * thanks to the GET parameters. - */ - - /** - * a boolean to know if the CAS client is running in callback mode. Written by - * CASClient::setCallBackMode(), read by CASClient::isCallbackMode(). - * - * @hideinitializer - * @private - */ - var $_callback_mode = FALSE; - - /** - * This method sets/unsets callback mode. - * - * @param $callback_mode TRUE to set callback mode, FALSE otherwise. - * - * @private - */ - function setCallbackMode($callback_mode) - { - $this->_callback_mode = $callback_mode; - } - - /** - * This method returns TRUE when the CAs client is running i callback mode, - * FALSE otherwise. - * - * @return A boolean. - * - * @private - */ - function isCallbackMode() - { - return $this->_callback_mode; - } - - /** - * the URL that should be used for the PGT callback (in fact the URL of the - * current request without any CGI parameter). Written and read by - * CASClient::getCallbackURL(). - * - * @hideinitializer - * @private - */ - var $_callback_url = ''; - - /** - * This method returns the URL that should be used for the PGT callback (in - * fact the URL of the current request without any CGI parameter, except if - * phpCAS::setFixedCallbackURL() was used). - * - * @return The callback URL - * - * @private - */ - function getCallbackURL() - { - // the URL is built when needed only - if ( empty($this->_callback_url) ) { - $final_uri = ''; - // remove the ticket if present in the URL - $final_uri = 'https://'; - /* replaced by Julien Marchal - v0.4.6 - * $this->uri .= $_SERVER['SERVER_NAME']; - */ - if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){ - /* replaced by teedog - v0.4.12 - * $final_uri .= $_SERVER['SERVER_NAME']; - */ - if (empty($_SERVER['SERVER_NAME'])) { - $final_uri .= $_SERVER['HTTP_HOST']; - } else { - $final_uri .= $_SERVER['SERVER_NAME']; - } - } else { - $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER']; - } - if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443) - || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) { - $final_uri .= ':'; - $final_uri .= $_SERVER['SERVER_PORT']; - } - $request_uri = $_SERVER['REQUEST_URI']; - $request_uri = preg_replace('/\?.*$/','',$request_uri); - $final_uri .= $request_uri; - $this->setCallbackURL($final_uri); - } - return $this->_callback_url; - } - - /** - * This method sets the callback url. - * - * @param $callback_url url to set callback - * - * @private - */ - function setCallbackURL($url) - { - return $this->_callback_url = $url; - } - - /** - * This method is called by CASClient::CASClient() when running in callback - * mode. It stores the PGT and its PGT Iou, prints its output and halts. - * - * @private - */ - function callback() - { - phpCAS::traceBegin(); - $this->printHTMLHeader('phpCAS callback'); - $pgt_iou = $_GET['pgtIou']; - $pgt = $_GET['pgtId']; - phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')'); - echo '

Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').

'; - $this->storePGT($pgt,$pgt_iou); - $this->printHTMLFooter(); - phpCAS::traceExit(); - } - - /** @} */ - - // ######################################################################## - // PGT STORAGE - // ######################################################################## - /** - * @addtogroup internalPGTStorage - * @{ - */ - - /** - * an instance of a class inheriting of PGTStorage, used to deal with PGT - * storage. Created by CASClient::setPGTStorageFile() or CASClient::setPGTStorageDB(), used - * by CASClient::setPGTStorageFile(), CASClient::setPGTStorageDB() and CASClient::initPGTStorage(). - * - * @hideinitializer - * @private - */ - var $_pgt_storage = null; - - /** - * This method is used to initialize the storage of PGT's. - * Halts on error. - * - * @private - */ - function initPGTStorage() - { - // if no SetPGTStorageXxx() has been used, default to file - if ( !is_object($this->_pgt_storage) ) { - $this->setPGTStorageFile(); - } - - // initializes the storage - $this->_pgt_storage->init(); - } - - /** - * This method stores a PGT. Halts on error. - * - * @param $pgt the PGT to store - * @param $pgt_iou its corresponding Iou - * - * @private - */ - function storePGT($pgt,$pgt_iou) - { - // ensure that storage is initialized - $this->initPGTStorage(); - // writes the PGT - $this->_pgt_storage->write($pgt,$pgt_iou); - } - - /** - * This method reads a PGT from its Iou and deletes the corresponding storage entry. - * - * @param $pgt_iou the PGT Iou - * - * @return The PGT corresponding to the Iou, FALSE when not found. - * - * @private - */ - function loadPGT($pgt_iou) - { - // ensure that storage is initialized - $this->initPGTStorage(); - // read the PGT - return $this->_pgt_storage->read($pgt_iou); - } - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests onto the filesystem. - * - * @param $format the format used to store the PGT's (`plain' and `xml' allowed) - * @param $path the path where the PGT's should be stored - * - * @public - */ - function setPGTStorageFile($format='', - $path='') - { - // check that the storage has not already been set - if ( is_object($this->_pgt_storage) ) { - phpCAS::error('PGT storage already defined'); - } - - // create the storage object - $this->_pgt_storage = &new PGTStorageFile($this,$format,$path); - } - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests into a database. - * @note The connection to the database is done only when needed. - * As a consequence, bad parameters are detected only when - * initializing PGT storage. - * - * @param $user the user to access the data with - * @param $password the user's password - * @param $database_type the type of the database hosting the data - * @param $hostname the server hosting the database - * @param $port the port the server is listening on - * @param $database the name of the database - * @param $table the name of the table storing the data - * - * @public - */ - function setPGTStorageDB($user, - $password, - $database_type, - $hostname, - $port, - $database, - $table) - { - // check that the storage has not already been set - if ( is_object($this->_pgt_storage) ) { - phpCAS::error('PGT storage already defined'); - } - - // warn the user that he should use file storage... - trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING); - - // create the storage object - $this->_pgt_storage = & new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table); - } - - // ######################################################################## - // PGT VALIDATION - // ######################################################################## - /** - * This method is used to validate a PGT; halt on failure. - * - * @param $validate_url the URL of the request to the CAS server. - * @param $text_response the response of the CAS server, as is (XML text); result - * of CASClient::validateST() or CASClient::validatePT(). - * @param $tree_response the response of the CAS server, as a DOM XML tree; result - * of CASClient::validateST() or CASClient::validatePT(). - * - * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). - * - * @private - */ - function validatePGT(&$validate_url,$text_response,$tree_response) - { - phpCAS::traceBegin(); - if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) { - phpCAS::trace(' not found'); - // authentication succeded, but no PGT Iou was transmitted - $this->authError('Ticket validated but no PGT Iou transmitted', - $validate_url, - FALSE/*$no_response*/, - FALSE/*$bad_response*/, - $text_response); - } else { - // PGT Iou transmitted, extract it - $pgt_iou = trim($arr[0]->get_content()); - $pgt = $this->loadPGT($pgt_iou); - if ( $pgt == FALSE ) { - phpCAS::trace('could not load PGT'); - $this->authError('PGT Iou was transmitted but PGT could not be retrieved', - $validate_url, - FALSE/*$no_response*/, - FALSE/*$bad_response*/, - $text_response); - } - $this->setPGT($pgt); - } - phpCAS::traceEnd(TRUE); - return TRUE; - } - - // ######################################################################## - // PGT VALIDATION - // ######################################################################## - - /** - * This method is used to retrieve PT's from the CAS server thanks to a PGT. - * - * @param $target_service the service to ask for with the PT. - * @param $err_code an error code (PHPCAS_SERVICE_OK on success). - * @param $err_msg an error message (empty on success). - * - * @return a Proxy Ticket, or FALSE on error. - * - * @private - */ - function retrievePT($target_service,&$err_code,&$err_msg) - { - phpCAS::traceBegin(); - - // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is - // set to false and $err_msg to an error message. At the end, if $pt is FALSE - // and $error_msg is still empty, it is set to 'invalid response' (the most - // commonly encountered error). - $err_msg = ''; - - // build the URL to retrieve the PT - // $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT(); - $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT(); - - // open and read the URL - if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) { - phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'); - $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; - $err_msg = 'could not retrieve PT (no response from the CAS server)'; - phpCAS::traceEnd(FALSE); - return FALSE; - } - - $bad_response = FALSE; - - if ( !$bad_response ) { - // read the response of the CAS server into a DOM object - if ( !($dom = @domxml_open_mem($cas_response))) { - phpCAS::trace('domxml_open_mem() failed'); - // read failed - $bad_response = TRUE; - } - } - - if ( !$bad_response ) { - // read the root node of the XML tree - if ( !($root = $dom->document_element()) ) { - phpCAS::trace('document_element() failed'); - // read failed - $bad_response = TRUE; - } - } - - if ( !$bad_response ) { - // insure that tag name is 'serviceResponse' - if ( $root->node_name() != 'serviceResponse' ) { - phpCAS::trace('node_name() failed'); - // bad root node - $bad_response = TRUE; - } - } - - if ( !$bad_response ) { - // look for a proxySuccess tag - if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) { - // authentication succeded, look for a proxyTicket tag - if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) { - $err_code = PHPCAS_SERVICE_OK; - $err_msg = ''; - phpCAS::trace('original PT: '.trim($arr[0]->get_content())); - $pt = trim($arr[0]->get_content()); - phpCAS::traceEnd($pt); - return $pt; - } else { - phpCAS::trace(' was found, but not '); - } - } - // look for a proxyFailure tag - else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) { - // authentication failed, extract the error - $err_code = PHPCAS_SERVICE_PT_FAILURE; - $err_msg = 'PT retrieving failed (code=`' - .$arr[0]->get_attribute('code') - .'\', message=`' - .trim($arr[0]->get_content()) - .'\')'; - phpCAS::traceEnd(FALSE); - return FALSE; - } else { - phpCAS::trace('neither nor found'); - } - } - - // at this step, we are sure that the response of the CAS server was ill-formed - $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; - $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')'; - - phpCAS::traceEnd(FALSE); - return FALSE; - } - - // ######################################################################## - // ACCESS TO EXTERNAL SERVICES - // ######################################################################## - - /** - * This method is used to acces a remote URL. - * - * @param $url the URL to access. - * @param $cookies an array containing cookies strings such as 'name=val' - * @param $headers an array containing the HTTP header lines of the response - * (an empty array on failure). - * @param $body the body of the response, as a string (empty on failure). - * @param $err_msg an error message, filled on failure. - * - * @return TRUE on success, FALSE otherwise (in this later case, $err_msg - * contains an error message). - * - * @private - */ - function readURL($url,$cookies,&$headers,&$body,&$err_msg) - { - phpCAS::traceBegin(); - $headers = ''; - $body = ''; - $err_msg = ''; - - $res = TRUE; - - // initialize the CURL session - $ch = curl_init($url); - - if (version_compare(PHP_VERSION,'5.1.3','>=')) { - //only avaible in php5 - curl_setopt_array($ch, $this->_curl_options); - } else { - foreach ($this->_curl_options as $key => $value) { - curl_setopt($ch, $key, $value); - } - } - - if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) { - phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'); - } - if ($this->_cas_server_cert != '' ) { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); - curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert); - } else if ($this->_cas_server_ca_cert != '') { - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); - curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert); - } else { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - } - - // return the CURL output into a variable - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - // get the HTTP header with a callback - $this->_curl_headers = array(); // empty the headers array - curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers')); - // add cookies headers - if ( is_array($cookies) ) { - curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies)); - } - // perform the query - $buf = curl_exec ($ch); - if ( $buf === FALSE ) { - phpCAS::trace('curl_exec() failed'); - $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch); - // close the CURL session - curl_close ($ch); - $res = FALSE; - } else { - // close the CURL session - curl_close ($ch); - - $headers = $this->_curl_headers; - $body = $buf; - } - - phpCAS::traceEnd($res); - return $res; - } - - /** - * This method is the callback used by readURL method to request HTTP headers. - */ - var $_curl_headers = array(); - function _curl_read_headers($ch, $header) - { - $this->_curl_headers[] = $header; - return strlen($header); - } - - /** - * This method is used to access an HTTP[S] service. - * - * @param $url the service to access. - * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on - * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. - * @param $output the output of the service (also used to give an error - * message on failure). - * - * @return TRUE on success, FALSE otherwise (in this later case, $err_code - * gives the reason why it failed and $output contains an error message). - * - * @public - */ - function serviceWeb($url,&$err_code,&$output) - { - phpCAS::traceBegin(); - // at first retrieve a PT - $pt = $this->retrievePT($url,$err_code,$output); - - $res = TRUE; - - // test if PT was retrieved correctly - if ( !$pt ) { - // note: $err_code and $err_msg are filled by CASClient::retrievePT() - phpCAS::trace('PT was not retrieved correctly'); - $res = FALSE; - } else { - // add cookies if necessary - if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) { - foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) { - $cookies[] = $name.'='.$val; - } - } - - // build the URL including the PT - if ( strstr($url,'?') === FALSE ) { - $service_url = $url.'?ticket='.$pt; - } else { - $service_url = $url.'&ticket='.$pt; - } - - phpCAS::trace('reading URL`'.$service_url.'\''); - if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) { - phpCAS::trace('could not read URL`'.$service_url.'\''); - $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; - // give an error message - $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), - $service_url, - $err_msg); - $res = FALSE; - } else { - // URL has been fetched, extract the cookies - phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:'); - foreach ( $headers as $header ) { - // test if the header is a cookie - if ( preg_match('/^Set-Cookie:/',$header) ) { - // the header is a cookie, remove the beginning - $header_val = preg_replace('/^Set-Cookie: */','',$header); - // extract interesting information - $name_val = strtok($header_val,'; '); - // extract the name and the value of the cookie - $cookie_name = strtok($name_val,'='); - $cookie_val = strtok('='); - // store the cookie - $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val; - phpCAS::trace($cookie_name.' -> '.$cookie_val); - } - } - } - } - - phpCAS::traceEnd($res); - return $res; - } - - /** - * This method is used to access an IMAP/POP3/NNTP service. - * - * @param $url a string giving the URL of the service, including the mailing box - * for IMAP URLs, as accepted by imap_open(). - * @param $flags options given to imap_open(). - * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on - * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. - * @param $err_msg an error message on failure - * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL - * on success, FALSE on error). - * - * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code - * gives the reason why it failed and $err_msg contains an error message). - * - * @public - */ - function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt) - { - phpCAS::traceBegin(); - // at first retrieve a PT - $pt = $this->retrievePT($target_service,$err_code,$output); - - $stream = FALSE; - - // test if PT was retrieved correctly - if ( !$pt ) { - // note: $err_code and $err_msg are filled by CASClient::retrievePT() - phpCAS::trace('PT was not retrieved correctly'); - } else { - phpCAS::trace('opening IMAP URL `'.$url.'\'...'); - $stream = @imap_open($url,$this->getUser(),$pt,$flags); - if ( !$stream ) { - phpCAS::trace('could not open URL'); - $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; - // give an error message - $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), - $service_url, - var_export(imap_errors(),TRUE)); - $pt = FALSE; - $stream = FALSE; - } else { - phpCAS::trace('ok'); - } - } - - phpCAS::traceEnd($stream); - return $stream; - } - - /** @} */ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX PROXIED CLIENT FEATURES (CAS 2.0) XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - // ######################################################################## - // PT - // ######################################################################## - /** - * @addtogroup internalProxied - * @{ - */ - - /** - * the Proxy Ticket provided in the URL of the request if present - * (empty otherwise). Written by CASClient::CASClient(), read by - * CASClient::getPT() and CASClient::hasPGT(). - * - * @hideinitializer - * @private - */ - var $_pt = ''; - - /** - * This method returns the Proxy Ticket provided in the URL of the request. - * @return The proxy ticket. - * @private - */ - function getPT() - { - // return 'ST'.substr($this->_pt, 2); - return $this->_pt; - } - - /** - * This method stores the Proxy Ticket. - * @param $pt The Proxy Ticket. - * @private - */ - function setPT($pt) - { $this->_pt = $pt; } - - /** - * This method tells if a Proxy Ticket was stored. - * @return TRUE if a Proxy Ticket has been stored. - * @private - */ - function hasPT() - { return !empty($this->_pt); } - - /** @} */ - // ######################################################################## - // PT VALIDATION - // ######################################################################## - /** - * @addtogroup internalProxied - * @{ - */ - - /** - * This method is used to validate a PT; halt on failure - * - * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). - * - * @private - */ - function validatePT(&$validate_url,&$text_response,&$tree_response) - { - phpCAS::traceBegin(); - // build the URL to validate the ticket - $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT(); - - if ( $this->isProxy() ) { - // pass the callback url for CAS proxies - $validate_url .= '&pgtUrl='.$this->getCallbackURL(); - } - - // open and read the URL - if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) { - phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); - $this->authError('PT not validated', - $validate_url, - TRUE/*$no_response*/); - } - - // read the response of the CAS server into a DOM object - if ( !($dom = domxml_open_mem($text_response))) { - // read failed - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - // read the root node of the XML tree - if ( !($tree_response = $dom->document_element()) ) { - // read failed - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - // insure that tag name is 'serviceResponse' - if ( $tree_response->node_name() != 'serviceResponse' ) { - // bad root node - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) { - // authentication succeded, extract the user name - if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) { - // no user specified => error - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - $this->setUser(trim($arr[0]->get_content())); - - } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) { - // authentication succeded, extract the error code and message - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - FALSE/*$bad_response*/, - $text_response, - $arr[0]->get_attribute('code')/*$err_code*/, - trim($arr[0]->get_content())/*$err_msg*/); - } else { - $this->authError('PT not validated', - $validate_url, - FALSE/*$no_response*/, - TRUE/*$bad_response*/, - $text_response); - } - - // at this step, PT has been validated and $this->_user has been set, - - phpCAS::traceEnd(TRUE); - return TRUE; - } - - /** @} */ - - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - // XX XX - // XX MISC XX - // XX XX - // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - /** - * @addtogroup internalMisc - * @{ - */ - - // ######################################################################## - // URL - // ######################################################################## - /** - * the URL of the current request (without any ticket CGI parameter). Written - * and read by CASClient::getURL(). - * - * @hideinitializer - * @private - */ - var $_url = ''; - - /** - * This method returns the URL of the current request (without any ticket - * CGI parameter). - * - * @return The URL - * - * @private - */ - function getURL() - { - phpCAS::traceBegin(); - // the URL is built when needed only - if ( empty($this->_url) ) { - $final_uri = ''; - // remove the ticket if present in the URL - $final_uri = ($this->isHttps()) ? 'https' : 'http'; - $final_uri .= '://'; - /* replaced by Julien Marchal - v0.4.6 - * $this->_url .= $_SERVER['SERVER_NAME']; - */ - if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){ - /* replaced by teedog - v0.4.12 - * $this->_url .= $_SERVER['SERVER_NAME']; - */ - if (empty($_SERVER['SERVER_NAME'])) { - $server_name = $_SERVER['HTTP_HOST']; - } else { - $server_name = $_SERVER['SERVER_NAME']; - } - } else { - $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER']; - } - $final_uri .= $server_name; - if (!strpos($server_name, ':')) { - if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443) - || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) { - $final_uri .= ':'; - $final_uri .= $_SERVER['SERVER_PORT']; - } - } - - $final_uri .= strtok($_SERVER['REQUEST_URI'],"?"); - $cgi_params = '?'.strtok("?"); - // remove the ticket if present in the CGI parameters - $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params); - $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params); - $cgi_params = preg_replace('/\?%26/','?',$cgi_params); - $cgi_params = preg_replace('/\?&/','?',$cgi_params); - $cgi_params = preg_replace('/\?$/','',$cgi_params); - $final_uri .= $cgi_params; - $this->setURL($final_uri); - } - phpCAS::traceEnd($this->_url); - return $this->_url; - } - - /** - * This method sets the URL of the current request - * - * @param $url url to set for service - * - * @private - */ - function setURL($url) - { - $this->_url = $url; - } - - // ######################################################################## - // AUTHENTICATION ERROR HANDLING - // ######################################################################## - /** - * This method is used to print the HTML output when the user was not authenticated. - * - * @param $failure the failure that occured - * @param $cas_url the URL the CAS server was asked for - * @param $no_response the response from the CAS server (other - * parameters are ignored if TRUE) - * @param $bad_response bad response from the CAS server ($err_code - * and $err_msg ignored if TRUE) - * @param $cas_response the response of the CAS server - * @param $err_code the error code given by the CAS server - * @param $err_msg the error message given by the CAS server - * - * @private - */ - function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='') - { - phpCAS::traceBegin(); - - $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED)); - printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']); - phpCAS::trace('CAS URL: '.$cas_url); - phpCAS::trace('Authentication failure: '.$failure); - if ( $no_response ) { - phpCAS::trace('Reason: no response from the CAS server'); - } else { - if ( $bad_response ) { - phpCAS::trace('Reason: bad response from the CAS server'); - } else { - switch ($this->getServerVersion()) { - case CAS_VERSION_1_0: - phpCAS::trace('Reason: CAS error'); - break; - case CAS_VERSION_2_0: - if ( empty($err_code) ) - phpCAS::trace('Reason: no CAS error'); - else - phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg); - break; - } - } - phpCAS::trace('CAS response: '.$cas_response); - } - $this->printHTMLFooter(); - phpCAS::traceExit(); - exit(); - } - - /** @} */ -} - + header('Location: '.$this->getURL()); + } + + //activate session mechanism if desired + if (!$this->isLogoutRequest() && $start_session) { + session_start(); + } + + $this->_proxy = $proxy; + + //check version + switch ($server_version) { + case CAS_VERSION_1_0: + if ( $this->isProxy() ) + phpCAS::error('CAS proxies are not supported in CAS ' + .$server_version); + break; + case CAS_VERSION_2_0: + break; + default: + phpCAS::error('this version of CAS (`' + .$server_version + .'\') is not supported by phpCAS ' + .phpCAS::getVersion()); + } + $this->_server['version'] = $server_version; + + //check hostname + if ( empty($server_hostname) + || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/',$server_hostname) ) { + phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')'); + } + $this->_server['hostname'] = $server_hostname; + + //check port + if ( $server_port == 0 + || !is_int($server_port) ) { + phpCAS::error('bad CAS server port (`'.$server_hostname.'\')'); + } + $this->_server['port'] = $server_port; + + //check URI + if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/',$server_uri) ) { + phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); + } + //add leading and trailing `/' and remove doubles + $server_uri = preg_replace('/\/\//','/','/'.$server_uri.'/'); + $this->_server['uri'] = $server_uri; + + //set to callback mode if PgtIou and PgtId CGI GET parameters are provided + if ( $this->isProxy() ) { + $this->setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])); + } + + if ( $this->isCallbackMode() ) { + //callback mode: check that phpCAS is secured + if ( !$this->isHttps() ) { + phpCAS::error('CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'); + } + } else { + //normal mode: get ticket and remove it from CGI parameters for developpers + $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null); + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: // check for a Service Ticket + if( preg_match('/^ST-/',$ticket) ) { + phpCAS::trace('ST \''.$ticket.'\' found'); + //ST present + $this->setST($ticket); + //ticket has been taken into account, unset it to hide it to applications + unset($_GET['ticket']); + } else if ( !empty($ticket) ) { + //ill-formed ticket, halt + phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')'); + } + break; + case CAS_VERSION_2_0: // check for a Service or Proxy Ticket + if( preg_match('/^[SP]T-/',$ticket) ) { + phpCAS::trace('ST or PT \''.$ticket.'\' found'); + $this->setPT($ticket); + unset($_GET['ticket']); + } else if ( !empty($ticket) ) { + //ill-formed ticket, halt + phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')'); + } + break; + } + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX AUTHENTICATION XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalAuthentication + * @{ + */ + + /** + * The Authenticated user. Written by CASClient::setUser(), read by CASClient::getUser(). + * @attention client applications should use phpCAS::getUser(). + * + * @hideinitializer + * @private + */ + var $_user = ''; + + /** + * This method sets the CAS user's login name. + * + * @param $user the login name of the authenticated user. + * + * @private + */ + function setUser($user) + { + $this->_user = $user; + } + + /** + * This method returns the CAS user's login name. + * @warning should be called only after CASClient::forceAuthentication() or + * CASClient::isAuthenticated(), otherwise halt with an error. + * + * @return the login name of the authenticated user + */ + function getUser() + { + if ( empty($this->_user) ) { + phpCAS::error('this method should be used only after '.__CLASS__.'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'); + } + return $this->_user; + } + + /** + * This method is called to renew the authentication of the user + * If the user is authenticated, renew the connection + * If not, redirect to CAS + * @public + */ + function renewAuthentication(){ + phpCAS::traceBegin(); + // Either way, the user is authenticated by CAS + if( isset( $_SESSION['phpCAS']['auth_checked'] ) ) + unset($_SESSION['phpCAS']['auth_checked']); + if ( $this->isAuthenticated() ) { + phpCAS::trace('user already authenticated; renew'); + $this->redirectToCas(false,true); + } else { + $this->redirectToCas(); + } + phpCAS::traceEnd(); + } + + /** + * This method is called to be sure that the user is authenticated. When not + * authenticated, halt by redirecting to the CAS server; otherwise return TRUE. + * @return TRUE when the user is authenticated; otherwise halt. + * @public + */ + function forceAuthentication() + { + phpCAS::traceBegin(); + + if ( $this->isAuthenticated() ) { + // the user is authenticated, nothing to be done. + phpCAS::trace('no need to authenticate'); + $res = TRUE; + } else { + // the user is not authenticated, redirect to the CAS server + if (isset($_SESSION['phpCAS']['auth_checked'])) { + unset($_SESSION['phpCAS']['auth_checked']); + } + $this->redirectToCas(FALSE/* no gateway */); + // never reached + $res = FALSE; + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * An integer that gives the number of times authentication will be cached before rechecked. + * + * @hideinitializer + * @private + */ + var $_cache_times_for_auth_recheck = 0; + + /** + * Set the number of times authentication will be cached before rechecked. + * + * @param $n an integer. + * + * @public + */ + function setCacheTimesForAuthRecheck($n) + { + $this->_cache_times_for_auth_recheck = $n; + } + + /** + * This method is called to check whether the user is authenticated or not. + * @return TRUE when the user is authenticated, FALSE otherwise. + * @public + */ + function checkAuthentication() + { + phpCAS::traceBegin(); + + if ( $this->isAuthenticated() ) { + phpCAS::trace('user is authenticated'); + $res = TRUE; + } else if (isset($_SESSION['phpCAS']['auth_checked'])) { + // the previous request has redirected the client to the CAS server with gateway=true + unset($_SESSION['phpCAS']['auth_checked']); + $res = FALSE; + } else { + // $_SESSION['phpCAS']['auth_checked'] = true; + // $this->redirectToCas(TRUE/* gateway */); + // // never reached + // $res = FALSE; + // avoid a check against CAS on every request + if (! isset($_SESSION['phpCAS']['unauth_count']) ) + $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized + + if (($_SESSION['phpCAS']['unauth_count'] != -2 && $this->_cache_times_for_auth_recheck == -1) + || ($_SESSION['phpCAS']['unauth_count'] >= 0 && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck)) + { + $res = FALSE; + + if ($this->_cache_times_for_auth_recheck != -1) + { + $_SESSION['phpCAS']['unauth_count']++; + phpCAS::trace('user is not authenticated (cached for '.$_SESSION['phpCAS']['unauth_count'].' times of '.$this->_cache_times_for_auth_recheck.')'); + } + else + { + phpCAS::trace('user is not authenticated (cached for until login pressed)'); + } + } + else + { + $_SESSION['phpCAS']['unauth_count'] = 0; + $_SESSION['phpCAS']['auth_checked'] = true; + phpCAS::trace('user is not authenticated (cache reset)'); + $this->redirectToCas(TRUE/* gateway */); + // never reached + $res = FALSE; + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @return TRUE when the user is authenticated. + * + * @public + */ + function isAuthenticated() + { + phpCAS::traceBegin(); + $res = FALSE; + $validate_url = ''; + + if ( $this->wasPreviouslyAuthenticated() ) { + // the user has already (previously during the session) been + // authenticated, nothing to be done. + phpCAS::trace('user was already authenticated, no need to look for tickets'); + $res = TRUE; + } + elseif ( $this->hasST() ) { + // if a Service Ticket was given, validate it + phpCAS::trace('ST `'.$this->getST().'\' is present'); + $this->validateST($validate_url,$text_response,$tree_response); // if it fails, it halts + phpCAS::trace('ST `'.$this->getST().'\' was validated'); + if ( $this->isProxy() ) { + $this->validatePGT($validate_url,$text_response,$tree_response); // idem + phpCAS::trace('PGT `'.$this->getPGT().'\' was validated'); + $_SESSION['phpCAS']['pgt'] = $this->getPGT(); + } + $_SESSION['phpCAS']['user'] = $this->getUser(); + $res = TRUE; + } + elseif ( $this->hasPT() ) { + // if a Proxy Ticket was given, validate it + phpCAS::trace('PT `'.$this->getPT().'\' is present'); + $this->validatePT($validate_url,$text_response,$tree_response); // note: if it fails, it halts + phpCAS::trace('PT `'.$this->getPT().'\' was validated'); + if ( $this->isProxy() ) { + $this->validatePGT($validate_url,$text_response,$tree_response); // idem + phpCAS::trace('PGT `'.$this->getPGT().'\' was validated'); + $_SESSION['phpCAS']['pgt'] = $this->getPGT(); + } + $_SESSION['phpCAS']['user'] = $this->getUser(); + $res = TRUE; + } + else { + // no ticket given, not authenticated + phpCAS::trace('no ticket found'); + } + + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method tells if the current session is authenticated. + * @return true if authenticated based soley on $_SESSION variable + * @since 0.4.22 by Brendan Arnold + */ + function isSessionAuthenticated () + { + return !empty($_SESSION['phpCAS']['user']); + } + + /** + * This method tells if the user has already been (previously) authenticated + * by looking into the session variables. + * + * @note This function switches to callback mode when needed. + * + * @return TRUE when the user has already been authenticated; FALSE otherwise. + * + * @private + */ + function wasPreviouslyAuthenticated() + { + phpCAS::traceBegin(); + + if ( $this->isCallbackMode() ) { + $this->callback(); + } + + $auth = FALSE; + + if ( $this->isProxy() ) { + // CAS proxy: username and PGT must be present + if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { + // authentication already done + $this->setUser($_SESSION['phpCAS']['user']); + $this->setPGT($_SESSION['phpCAS']['pgt']); + phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\''); + $auth = TRUE; + } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace('username found (`'.$_SESSION['phpCAS']['user'].'\') but PGT is empty'); + // unset all tickets to enforce authentication + unset($_SESSION['phpCAS']); + $this->setST(''); + $this->setPT(''); + } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace('PGT found (`'.$_SESSION['phpCAS']['pgt'].'\') but username is empty'); + // unset all tickets to enforce authentication + unset($_SESSION['phpCAS']); + $this->setST(''); + $this->setPT(''); + } else { + phpCAS::trace('neither user not PGT found'); + } + } else { + // `simple' CAS client (not a proxy): username must be present + if ( $this->isSessionAuthenticated() ) { + // authentication already done + $this->setUser($_SESSION['phpCAS']['user']); + phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\''); + $auth = TRUE; + } else { + phpCAS::trace('no user found'); + } + } + + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * This method is used to redirect the client to the CAS server. + * It is used by CASClient::forceAuthentication() and CASClient::checkAuthentication(). + * @param $gateway true to check authentication, false to force it + * @param $renew true to force the authentication with the CAS server + * @public + */ + function redirectToCas($gateway=false,$renew=false){ + phpCAS::traceBegin(); + $cas_url = $this->getServerLoginURL($gateway,$renew); + header('Location: '.$cas_url); + phpCAS::log( "Redirect to : ".$cas_url ); + + $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_WANTED)); + + printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + exit(); + } + +// /** +// * This method is used to logout from CAS. +// * @param $url a URL that will be transmitted to the CAS server (to come back to when logged out) +// * @public +// */ +// function logout($url = "") { +// phpCAS::traceBegin(); +// $cas_url = $this->getServerLogoutURL(); +// // v0.4.14 sebastien.gougeon at univ-rennes1.fr +// // header('Location: '.$cas_url); +// if ( $url != "" ) { +// // Adam Moore 1.0.0RC2 +// $url = '?service=' . $url . '&url=' . $url; +// } +// header('Location: '.$cas_url . $url); +// session_unset(); +// session_destroy(); +// $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT)); +// printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); +// $this->printHTMLFooter(); +// phpCAS::traceExit(); +// exit(); +// } + + /** + * This method is used to logout from CAS. + * @params $params an array that contains the optional url and service parameters that will be passed to the CAS server + * @public + */ + function logout($params) { + phpCAS::traceBegin(); + $cas_url = $this->getServerLogoutURL(); + $paramSeparator = '?'; + if (isset($params['url'])) { + $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($params['url']); + $paramSeparator = '&'; + } + if (isset($params['service'])) { + $cas_url = $cas_url . $paramSeparator . "service=" . urlencode($params['service']); + } + header('Location: '.$cas_url); + session_unset(); + session_destroy(); + $this->printHTMLHeader($this->getString(CAS_STR_LOGOUT)); + printf('

'.$this->getString(CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED).'

',$cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + exit(); + } + + /** + * @return true if the current request is a logout request. + * @private + */ + function isLogoutRequest() { + return !empty($_POST['logoutRequest']); + } + + /** + * @return true if a logout request is allowed. + * @private + */ + function isLogoutRequestAllowed() { + } + + /** + * This method handles logout requests. + * @param $check_client true to check the client bofore handling the request, + * false not to perform any access control. True by default. + * @param $allowed_clients an array of host names allowed to send logout requests. + * By default, only the CAs server (declared in the constructor) will be allowed. + * @public + */ + function handleLogoutRequests($check_client=true, $allowed_clients=false) { + phpCAS::traceBegin(); + if (!$this->isLogoutRequest()) { + phpCAS::log("Not a logout request"); + phpCAS::traceEnd(); + return; + } + phpCAS::log("Logout requested"); + phpCAS::log("SAML REQUEST: ".$_POST['logoutRequest']); + if ($check_client) { + if (!$allowed_clients) { + $allowed_clients = array( $this->getServerHostname() ); + } + $client_ip = $_SERVER['REMOTE_ADDR']; + $client = gethostbyaddr($client_ip); + phpCAS::log("Client: ".$client); + $allowed = false; + foreach ($allowed_clients as $allowed_client) { + if ($client == $allowed_client) { + phpCAS::log("Allowed client '".$allowed_client."' matches, logout request is allowed"); + $allowed = true; + break; + } else { + phpCAS::log("Allowed client '".$allowed_client."' does not match"); + } + } + if (!$allowed) { + phpCAS::error("Unauthorized logout request from client '".$client."'"); + printf("Unauthorized!"); + phpCAS::traceExit(); + exit(); + } + } else { + phpCAS::log("No access control set"); + } + // Extract the ticket from the SAML Request + preg_match("|(.*)|", $_POST['logoutRequest'], $tick, PREG_OFFSET_CAPTURE, 3); + $wrappedSamlSessionIndex = preg_replace('||','',$tick[0][0]); + $ticket2logout = preg_replace('||','',$wrappedSamlSessionIndex); + phpCAS::log("Ticket to logout: ".$ticket2logout); + $session_id = preg_replace('/[^\w]/','',$ticket2logout); + phpCAS::log("Session id: ".$session_id); + + // fix New session ID + session_id($session_id); + $_COOKIE[session_name()]=$session_id; + $_GET[session_name()]=$session_id; + + // Overwrite session + session_start(); + session_unset(); + session_destroy(); + printf("Disconnected!"); + phpCAS::traceExit(); + exit(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX BASIC CLIENT FEATURES (CAS 1.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // ST + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * the Service Ticket provided in the URL of the request if present + * (empty otherwise). Written by CASClient::CASClient(), read by + * CASClient::getST() and CASClient::hasPGT(). + * + * @hideinitializer + * @private + */ + var $_st = ''; + + /** + * This method returns the Service Ticket provided in the URL of the request. + * @return The service ticket. + * @private + */ + function getST() + { return $this->_st; } + + /** + * This method stores the Service Ticket. + * @param $st The Service Ticket. + * @private + */ + function setST($st) + { $this->_st = $st; } + + /** + * This method tells if a Service Ticket was stored. + * @return TRUE if a Service Ticket has been stored. + * @private + */ + function hasST() + { return !empty($this->_st); } + + /** @} */ + + // ######################################################################## + // ST VALIDATION + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * the certificate of the CAS server. + * + * @hideinitializer + * @private + */ + var $_cas_server_cert = ''; + + /** + * the certificate of the CAS server CA. + * + * @hideinitializer + * @private + */ + var $_cas_server_ca_cert = ''; + + /** + * Set to true not to validate the CAS server. + * + * @hideinitializer + * @private + */ + var $_no_cas_server_validation = false; + + /** + * Set the certificate of the CAS server. + * + * @param $cert the PEM certificate + */ + function setCasServerCert($cert) + { + $this->_cas_server_cert = $cert; + } + + /** + * Set the CA certificate of the CAS server. + * + * @param $cert the PEM certificate of the CA that emited the cert of the server + */ + function setCasServerCACert($cert) + { + $this->_cas_server_ca_cert = $cert; + } + + /** + * Set no SSL validation for the CAS server. + */ + function setNoCasServerValidation() + { + $this->_no_cas_server_validation = true; + } + + /** + * This method is used to validate a ST; halt on failure, and sets $validate_url, + * $text_reponse and $tree_response on success. These parameters are used later + * by CASClient::validatePGT() for CAS proxies. + * + * @param $validate_url the URL of the request to the CAS server. + * @param $text_response the response of the CAS server, as is (XML text). + * @param $tree_response the response of the CAS server, as a DOM XML tree. + * + * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). + * + * @private + */ + function validateST($validate_url,&$text_response,&$tree_response) + { + phpCAS::traceBegin(); + // build the URL to validate the ticket + $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getST(); + if ( $this->isProxy() ) { + // pass the callback url for CAS proxies + $validate_url .= '&pgtUrl='.$this->getCallbackURL(); + } + + // open and read the URL + if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) { + phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); + $this->authError('ST not validated', + $validate_url, + TRUE/*$no_response*/); + } + + // analyze the result depending on the version + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + if (preg_match('/^no\n/',$text_response)) { + phpCAS::trace('ST has not been validated'); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + FALSE/*$bad_response*/, + $text_response); + } + if (!preg_match('/^yes\n/',$text_response)) { + phpCAS::trace('ill-formed response'); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + // ST has been validated, extract the user name + $arr = preg_split('/\n/',$text_response); + $this->setUser(trim($arr[1])); + break; + case CAS_VERSION_2_0: + // read the response of the CAS server into a DOM object + if ( !($dom = domxml_open_mem($text_response))) { + phpCAS::trace('domxml_open_mem() failed'); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + // read the root node of the XML tree + if ( !($tree_response = $dom->document_element()) ) { + phpCAS::trace('document_element() failed'); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + // insure that tag name is 'serviceResponse' + if ( $tree_response->node_name() != 'serviceResponse' ) { + phpCAS::trace('bad XML root node (should be `serviceResponse\' instead of `'.$tree_response->node_name().'\''); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + if ( sizeof($success_elements = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) { + // authentication succeded, extract the user name + if ( sizeof($user_elements = $success_elements[0]->get_elements_by_tagname("user")) == 0) { + phpCAS::trace(' found, but no '); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + $user = trim($user_elements[0]->get_content()); + phpCAS::trace('user = `'.$user); + $this->setUser($user); + + } else if ( sizeof($failure_elements = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) { + phpCAS::trace(' found'); + // authentication failed, extract the error code and message + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + FALSE/*$bad_response*/, + $text_response, + $failure_elements[0]->get_attribute('code')/*$err_code*/, + trim($failure_elements[0]->get_content())/*$err_msg*/); + } else { + phpCAS::trace('neither nor found'); + $this->authError('ST not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + break; + } + + // at this step, ST has been validated and $this->_user has been set, + phpCAS::traceEnd(TRUE); + return TRUE; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXY FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PROXYING + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * A boolean telling if the client is a CAS proxy or not. Written by CASClient::CASClient(), + * read by CASClient::isProxy(). + * + * @private + */ + var $_proxy; + + /** + * Tells if a CAS client is a CAS proxy or not + * + * @return TRUE when the CAS client is a CAs proxy, FALSE otherwise + * + * @private + */ + function isProxy() + { + return $this->_proxy; + } + + /** @} */ + // ######################################################################## + // PGT + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * the Proxy Grnting Ticket given by the CAS server (empty otherwise). + * Written by CASClient::setPGT(), read by CASClient::getPGT() and CASClient::hasPGT(). + * + * @hideinitializer + * @private + */ + var $_pgt = ''; + + /** + * This method returns the Proxy Granting Ticket given by the CAS server. + * @return The Proxy Granting Ticket. + * @private + */ + function getPGT() + { return $this->_pgt; } + + /** + * This method stores the Proxy Granting Ticket. + * @param $pgt The Proxy Granting Ticket. + * @private + */ + function setPGT($pgt) + { $this->_pgt = $pgt; } + + /** + * This method tells if a Proxy Granting Ticket was stored. + * @return TRUE if a Proxy Granting Ticket has been stored. + * @private + */ + function hasPGT() + { return !empty($this->_pgt); } + + /** @} */ + + // ######################################################################## + // CALLBACK MODE + // ######################################################################## + /** + * @addtogroup internalCallback + * @{ + */ + /** + * each PHP script using phpCAS in proxy mode is its own callback to get the + * PGT back from the CAS server. callback_mode is detected by the constructor + * thanks to the GET parameters. + */ + + /** + * a boolean to know if the CAS client is running in callback mode. Written by + * CASClient::setCallBackMode(), read by CASClient::isCallbackMode(). + * + * @hideinitializer + * @private + */ + var $_callback_mode = FALSE; + + /** + * This method sets/unsets callback mode. + * + * @param $callback_mode TRUE to set callback mode, FALSE otherwise. + * + * @private + */ + function setCallbackMode($callback_mode) + { + $this->_callback_mode = $callback_mode; + } + + /** + * This method returns TRUE when the CAs client is running i callback mode, + * FALSE otherwise. + * + * @return A boolean. + * + * @private + */ + function isCallbackMode() + { + return $this->_callback_mode; + } + + /** + * the URL that should be used for the PGT callback (in fact the URL of the + * current request without any CGI parameter). Written and read by + * CASClient::getCallbackURL(). + * + * @hideinitializer + * @private + */ + var $_callback_url = ''; + + /** + * This method returns the URL that should be used for the PGT callback (in + * fact the URL of the current request without any CGI parameter, except if + * phpCAS::setFixedCallbackURL() was used). + * + * @return The callback URL + * + * @private + */ + function getCallbackURL() + { + // the URL is built when needed only + if ( empty($this->_callback_url) ) { + $final_uri = ''; + // remove the ticket if present in the URL + $final_uri = 'https://'; + /* replaced by Julien Marchal - v0.4.6 + * $this->uri .= $_SERVER['SERVER_NAME']; + */ + if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){ + /* replaced by teedog - v0.4.12 + * $final_uri .= $_SERVER['SERVER_NAME']; + */ + if (empty($_SERVER['SERVER_NAME'])) { + $final_uri .= $_SERVER['HTTP_HOST']; + } else { + $final_uri .= $_SERVER['SERVER_NAME']; + } + } else { + $final_uri .= $_SERVER['HTTP_X_FORWARDED_SERVER']; + } + if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443) + || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) { + $final_uri .= ':'; + $final_uri .= $_SERVER['SERVER_PORT']; + } + $request_uri = $_SERVER['REQUEST_URI']; + $request_uri = preg_replace('/\?.*$/','',$request_uri); + $final_uri .= $request_uri; + $this->setCallbackURL($final_uri); + } + return $this->_callback_url; + } + + /** + * This method sets the callback url. + * + * @param $callback_url url to set callback + * + * @private + */ + function setCallbackURL($url) + { + return $this->_callback_url = $url; + } + + /** + * This method is called by CASClient::CASClient() when running in callback + * mode. It stores the PGT and its PGT Iou, prints its output and halts. + * + * @private + */ + function callback() + { + phpCAS::traceBegin(); + $this->printHTMLHeader('phpCAS callback'); + $pgt_iou = $_GET['pgtIou']; + $pgt = $_GET['pgtId']; + phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')'); + echo '

Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').

'; + $this->storePGT($pgt,$pgt_iou); + $this->printHTMLFooter(); + phpCAS::traceExit(); + } + + /** @} */ + + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup internalPGTStorage + * @{ + */ + + /** + * an instance of a class inheriting of PGTStorage, used to deal with PGT + * storage. Created by CASClient::setPGTStorageFile() or CASClient::setPGTStorageDB(), used + * by CASClient::setPGTStorageFile(), CASClient::setPGTStorageDB() and CASClient::initPGTStorage(). + * + * @hideinitializer + * @private + */ + var $_pgt_storage = null; + + /** + * This method is used to initialize the storage of PGT's. + * Halts on error. + * + * @private + */ + function initPGTStorage() + { + // if no SetPGTStorageXxx() has been used, default to file + if ( !is_object($this->_pgt_storage) ) { + $this->setPGTStorageFile(); + } + + // initializes the storage + $this->_pgt_storage->init(); + } + + /** + * This method stores a PGT. Halts on error. + * + * @param $pgt the PGT to store + * @param $pgt_iou its corresponding Iou + * + * @private + */ + function storePGT($pgt,$pgt_iou) + { + // ensure that storage is initialized + $this->initPGTStorage(); + // writes the PGT + $this->_pgt_storage->write($pgt,$pgt_iou); + } + + /** + * This method reads a PGT from its Iou and deletes the corresponding storage entry. + * + * @param $pgt_iou the PGT Iou + * + * @return The PGT corresponding to the Iou, FALSE when not found. + * + * @private + */ + function loadPGT($pgt_iou) + { + // ensure that storage is initialized + $this->initPGTStorage(); + // read the PGT + return $this->_pgt_storage->read($pgt_iou); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param $format the format used to store the PGT's (`plain' and `xml' allowed) + * @param $path the path where the PGT's should be stored + * + * @public + */ + function setPGTStorageFile($format='', + $path='') + { + // check that the storage has not already been set + if ( is_object($this->_pgt_storage) ) { + phpCAS::error('PGT storage already defined'); + } + + // create the storage object + $this->_pgt_storage = &new PGTStorageFile($this,$format,$path); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests into a database. + * @note The connection to the database is done only when needed. + * As a consequence, bad parameters are detected only when + * initializing PGT storage. + * + * @param $user the user to access the data with + * @param $password the user's password + * @param $database_type the type of the database hosting the data + * @param $hostname the server hosting the database + * @param $port the port the server is listening on + * @param $database the name of the database + * @param $table the name of the table storing the data + * + * @public + */ + function setPGTStorageDB($user, + $password, + $database_type, + $hostname, + $port, + $database, + $table) + { + // check that the storage has not already been set + if ( is_object($this->_pgt_storage) ) { + phpCAS::error('PGT storage already defined'); + } + + // warn the user that he should use file storage... + trigger_error('PGT storage into database is an experimental feature, use at your own risk',E_USER_WARNING); + + // create the storage object + $this->_pgt_storage = & new PGTStorageDB($this,$user,$password,$database_type,$hostname,$port,$database,$table); + } + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + /** + * This method is used to validate a PGT; halt on failure. + * + * @param $validate_url the URL of the request to the CAS server. + * @param $text_response the response of the CAS server, as is (XML text); result + * of CASClient::validateST() or CASClient::validatePT(). + * @param $tree_response the response of the CAS server, as a DOM XML tree; result + * of CASClient::validateST() or CASClient::validatePT(). + * + * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). + * + * @private + */ + function validatePGT(&$validate_url,$text_response,$tree_response) + { + phpCAS::traceBegin(); + if ( sizeof($arr = $tree_response->get_elements_by_tagname("proxyGrantingTicket")) == 0) { + phpCAS::trace(' not found'); + // authentication succeded, but no PGT Iou was transmitted + $this->authError('Ticket validated but no PGT Iou transmitted', + $validate_url, + FALSE/*$no_response*/, + FALSE/*$bad_response*/, + $text_response); + } else { + // PGT Iou transmitted, extract it + $pgt_iou = trim($arr[0]->get_content()); + $pgt = $this->loadPGT($pgt_iou); + if ( $pgt == FALSE ) { + phpCAS::trace('could not load PGT'); + $this->authError('PGT Iou was transmitted but PGT could not be retrieved', + $validate_url, + FALSE/*$no_response*/, + FALSE/*$bad_response*/, + $text_response); + } + $this->setPGT($pgt); + } + phpCAS::traceEnd(TRUE); + return TRUE; + } + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + + /** + * This method is used to retrieve PT's from the CAS server thanks to a PGT. + * + * @param $target_service the service to ask for with the PT. + * @param $err_code an error code (PHPCAS_SERVICE_OK on success). + * @param $err_msg an error message (empty on success). + * + * @return a Proxy Ticket, or FALSE on error. + * + * @private + */ + function retrievePT($target_service,&$err_code,&$err_msg) + { + phpCAS::traceBegin(); + + // by default, $err_msg is set empty and $pt to TRUE. On error, $pt is + // set to false and $err_msg to an error message. At the end, if $pt is FALSE + // and $error_msg is still empty, it is set to 'invalid response' (the most + // commonly encountered error). + $err_msg = ''; + + // build the URL to retrieve the PT + // $cas_url = $this->getServerProxyURL().'?targetService='.preg_replace('/&/','%26',$target_service).'&pgt='.$this->getPGT(); + $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($target_service).'&pgt='.$this->getPGT(); + + // open and read the URL + if ( !$this->readURL($cas_url,''/*cookies*/,$headers,$cas_response,$err_msg) ) { + phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'); + $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; + $err_msg = 'could not retrieve PT (no response from the CAS server)'; + phpCAS::traceEnd(FALSE); + return FALSE; + } + + $bad_response = FALSE; + + if ( !$bad_response ) { + // read the response of the CAS server into a DOM object + if ( !($dom = @domxml_open_mem($cas_response))) { + phpCAS::trace('domxml_open_mem() failed'); + // read failed + $bad_response = TRUE; + } + } + + if ( !$bad_response ) { + // read the root node of the XML tree + if ( !($root = $dom->document_element()) ) { + phpCAS::trace('document_element() failed'); + // read failed + $bad_response = TRUE; + } + } + + if ( !$bad_response ) { + // insure that tag name is 'serviceResponse' + if ( $root->node_name() != 'serviceResponse' ) { + phpCAS::trace('node_name() failed'); + // bad root node + $bad_response = TRUE; + } + } + + if ( !$bad_response ) { + // look for a proxySuccess tag + if ( sizeof($arr = $root->get_elements_by_tagname("proxySuccess")) != 0) { + // authentication succeded, look for a proxyTicket tag + if ( sizeof($arr = $root->get_elements_by_tagname("proxyTicket")) != 0) { + $err_code = PHPCAS_SERVICE_OK; + $err_msg = ''; + phpCAS::trace('original PT: '.trim($arr[0]->get_content())); + $pt = trim($arr[0]->get_content()); + phpCAS::traceEnd($pt); + return $pt; + } else { + phpCAS::trace(' was found, but not '); + } + } + // look for a proxyFailure tag + else if ( sizeof($arr = $root->get_elements_by_tagname("proxyFailure")) != 0) { + // authentication failed, extract the error + $err_code = PHPCAS_SERVICE_PT_FAILURE; + $err_msg = 'PT retrieving failed (code=`' + .$arr[0]->get_attribute('code') + .'\', message=`' + .trim($arr[0]->get_content()) + .'\')'; + phpCAS::traceEnd(FALSE); + return FALSE; + } else { + phpCAS::trace('neither nor found'); + } + } + + // at this step, we are sure that the response of the CAS server was ill-formed + $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; + $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')'; + + phpCAS::traceEnd(FALSE); + return FALSE; + } + + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + + /** + * This method is used to acces a remote URL. + * + * @param $url the URL to access. + * @param $cookies an array containing cookies strings such as 'name=val' + * @param $headers an array containing the HTTP header lines of the response + * (an empty array on failure). + * @param $body the body of the response, as a string (empty on failure). + * @param $err_msg an error message, filled on failure. + * + * @return TRUE on success, FALSE otherwise (in this later case, $err_msg + * contains an error message). + * + * @private + */ + function readURL($url,$cookies,&$headers,&$body,&$err_msg) + { + phpCAS::traceBegin(); + $headers = ''; + $body = ''; + $err_msg = ''; + + $res = TRUE; + + // initialize the CURL session + $ch = curl_init($url); + + if (version_compare(PHP_VERSION,'5.1.3','>=')) { + //only avaible in php5 + curl_setopt_array($ch, $this->_curl_options); + } else { + foreach ($this->_curl_options as $key => $value) { + curl_setopt($ch, $key, $value); + } + } + + if ($this->_cas_server_cert == '' && $this->_cas_server_ca_cert == '' && !$this->_no_cas_server_validation) { + phpCAS::error('one of the methods phpCAS::setCasServerCert(), phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'); + } + if ($this->_cas_server_cert != '' ) { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_SSLCERT, $this->_cas_server_cert); + } else if ($this->_cas_server_ca_cert != '') { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_CAINFO, $this->_cas_server_ca_cert); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + } + + // return the CURL output into a variable + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + // get the HTTP header with a callback + $this->_curl_headers = array(); // empty the headers array + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curl_read_headers')); + // add cookies headers + if ( is_array($cookies) ) { + curl_setopt($ch,CURLOPT_COOKIE,implode(';',$cookies)); + } + // perform the query + $buf = curl_exec ($ch); + if ( $buf === FALSE ) { + phpCAS::trace('curl_exec() failed'); + $err_msg = 'CURL error #'.curl_errno($ch).': '.curl_error($ch); + // close the CURL session + curl_close ($ch); + $res = FALSE; + } else { + // close the CURL session + curl_close ($ch); + + $headers = $this->_curl_headers; + $body = $buf; + } + + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is the callback used by readURL method to request HTTP headers. + */ + var $_curl_headers = array(); + function _curl_read_headers($ch, $header) + { + $this->_curl_headers[] = $header; + return strlen($header); + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param $url the service to access. + * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on + * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. + * @param $output the output of the service (also used to give an error + * message on failure). + * + * @return TRUE on success, FALSE otherwise (in this later case, $err_code + * gives the reason why it failed and $output contains an error message). + * + * @public + */ + function serviceWeb($url,&$err_code,&$output) + { + phpCAS::traceBegin(); + // at first retrieve a PT + $pt = $this->retrievePT($url,$err_code,$output); + + $res = TRUE; + + // test if PT was retrieved correctly + if ( !$pt ) { + // note: $err_code and $err_msg are filled by CASClient::retrievePT() + phpCAS::trace('PT was not retrieved correctly'); + $res = FALSE; + } else { + // add cookies if necessary + if ( is_array($_SESSION['phpCAS']['services'][$url]['cookies']) ) { + foreach ( $_SESSION['phpCAS']['services'][$url]['cookies'] as $name => $val ) { + $cookies[] = $name.'='.$val; + } + } + + // build the URL including the PT + if ( strstr($url,'?') === FALSE ) { + $service_url = $url.'?ticket='.$pt; + } else { + $service_url = $url.'&ticket='.$pt; + } + + phpCAS::trace('reading URL`'.$service_url.'\''); + if ( !$this->readURL($service_url,$cookies,$headers,$output,$err_msg) ) { + phpCAS::trace('could not read URL`'.$service_url.'\''); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + // give an error message + $output = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), + $service_url, + $err_msg); + $res = FALSE; + } else { + // URL has been fetched, extract the cookies + phpCAS::trace('URL`'.$service_url.'\' has been read, storing cookies:'); + foreach ( $headers as $header ) { + // test if the header is a cookie + if ( preg_match('/^Set-Cookie:/',$header) ) { + // the header is a cookie, remove the beginning + $header_val = preg_replace('/^Set-Cookie: */','',$header); + // extract interesting information + $name_val = strtok($header_val,'; '); + // extract the name and the value of the cookie + $cookie_name = strtok($name_val,'='); + $cookie_val = strtok('='); + // store the cookie + $_SESSION['phpCAS']['services'][$url]['cookies'][$cookie_name] = $cookie_val; + phpCAS::trace($cookie_name.' -> '.$cookie_val); + } + } + } + } + + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param $url a string giving the URL of the service, including the mailing box + * for IMAP URLs, as accepted by imap_open(). + * @param $flags options given to imap_open(). + * @param $err_code an error code Possible values are PHPCAS_SERVICE_OK (on + * success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_NOT AVAILABLE. + * @param $err_msg an error message on failure + * @param $pt the Proxy Ticket (PT) retrieved from the CAS server to access the URL + * on success, FALSE on error). + * + * @return an IMAP stream on success, FALSE otherwise (in this later case, $err_code + * gives the reason why it failed and $err_msg contains an error message). + * + * @public + */ + function serviceMail($url,$flags,&$err_code,&$err_msg,&$pt) + { + phpCAS::traceBegin(); + // at first retrieve a PT + $pt = $this->retrievePT($target_service,$err_code,$output); + + $stream = FALSE; + + // test if PT was retrieved correctly + if ( !$pt ) { + // note: $err_code and $err_msg are filled by CASClient::retrievePT() + phpCAS::trace('PT was not retrieved correctly'); + } else { + phpCAS::trace('opening IMAP URL `'.$url.'\'...'); + $stream = @imap_open($url,$this->getUser(),$pt,$flags); + if ( !$stream ) { + phpCAS::trace('could not open URL'); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + // give an error message + $err_msg = sprintf($this->getString(CAS_STR_SERVICE_UNAVAILABLE), + $service_url, + var_export(imap_errors(),TRUE)); + $pt = FALSE; + $stream = FALSE; + } else { + phpCAS::trace('ok'); + } + } + + phpCAS::traceEnd($stream); + return $stream; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXIED CLIENT FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PT + // ######################################################################## + /** + * @addtogroup internalProxied + * @{ + */ + + /** + * the Proxy Ticket provided in the URL of the request if present + * (empty otherwise). Written by CASClient::CASClient(), read by + * CASClient::getPT() and CASClient::hasPGT(). + * + * @hideinitializer + * @private + */ + var $_pt = ''; + + /** + * This method returns the Proxy Ticket provided in the URL of the request. + * @return The proxy ticket. + * @private + */ + function getPT() + { + // return 'ST'.substr($this->_pt, 2); + return $this->_pt; + } + + /** + * This method stores the Proxy Ticket. + * @param $pt The Proxy Ticket. + * @private + */ + function setPT($pt) + { $this->_pt = $pt; } + + /** + * This method tells if a Proxy Ticket was stored. + * @return TRUE if a Proxy Ticket has been stored. + * @private + */ + function hasPT() + { return !empty($this->_pt); } + + /** @} */ + // ######################################################################## + // PT VALIDATION + // ######################################################################## + /** + * @addtogroup internalProxied + * @{ + */ + + /** + * This method is used to validate a PT; halt on failure + * + * @return bool TRUE when successfull, halt otherwise by calling CASClient::authError(). + * + * @private + */ + function validatePT(&$validate_url,&$text_response,&$tree_response) + { + phpCAS::traceBegin(); + // build the URL to validate the ticket + $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getPT(); + + if ( $this->isProxy() ) { + // pass the callback url for CAS proxies + $validate_url .= '&pgtUrl='.$this->getCallbackURL(); + } + + // open and read the URL + if ( !$this->readURL($validate_url,''/*cookies*/,$headers,$text_response,$err_msg) ) { + phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'); + $this->authError('PT not validated', + $validate_url, + TRUE/*$no_response*/); + } + + // read the response of the CAS server into a DOM object + if ( !($dom = domxml_open_mem($text_response))) { + // read failed + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + // read the root node of the XML tree + if ( !($tree_response = $dom->document_element()) ) { + // read failed + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + // insure that tag name is 'serviceResponse' + if ( $tree_response->node_name() != 'serviceResponse' ) { + // bad root node + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationSuccess")) != 0) { + // authentication succeded, extract the user name + if ( sizeof($arr = $tree_response->get_elements_by_tagname("user")) == 0) { + // no user specified => error + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + $this->setUser(trim($arr[0]->get_content())); + + } else if ( sizeof($arr = $tree_response->get_elements_by_tagname("authenticationFailure")) != 0) { + // authentication succeded, extract the error code and message + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + FALSE/*$bad_response*/, + $text_response, + $arr[0]->get_attribute('code')/*$err_code*/, + trim($arr[0]->get_content())/*$err_msg*/); + } else { + $this->authError('PT not validated', + $validate_url, + FALSE/*$no_response*/, + TRUE/*$bad_response*/, + $text_response); + } + + // at this step, PT has been validated and $this->_user has been set, + + phpCAS::traceEnd(TRUE); + return TRUE; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX MISC XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalMisc + * @{ + */ + + // ######################################################################## + // URL + // ######################################################################## + /** + * the URL of the current request (without any ticket CGI parameter). Written + * and read by CASClient::getURL(). + * + * @hideinitializer + * @private + */ + var $_url = ''; + + /** + * This method returns the URL of the current request (without any ticket + * CGI parameter). + * + * @return The URL + * + * @private + */ + function getURL() + { + phpCAS::traceBegin(); + // the URL is built when needed only + if ( empty($this->_url) ) { + $final_uri = ''; + // remove the ticket if present in the URL + $final_uri = ($this->isHttps()) ? 'https' : 'http'; + $final_uri .= '://'; + /* replaced by Julien Marchal - v0.4.6 + * $this->_url .= $_SERVER['SERVER_NAME']; + */ + if(empty($_SERVER['HTTP_X_FORWARDED_SERVER'])){ + /* replaced by teedog - v0.4.12 + * $this->_url .= $_SERVER['SERVER_NAME']; + */ + if (empty($_SERVER['SERVER_NAME'])) { + $server_name = $_SERVER['HTTP_HOST']; + } else { + $server_name = $_SERVER['SERVER_NAME']; + } + } else { + $server_name = $_SERVER['HTTP_X_FORWARDED_SERVER']; + } + $final_uri .= $server_name; + if (!strpos($server_name, ':')) { + if ( ($this->isHttps() && $_SERVER['SERVER_PORT']!=443) + || (!$this->isHttps() && $_SERVER['SERVER_PORT']!=80) ) { + $final_uri .= ':'; + $final_uri .= $_SERVER['SERVER_PORT']; + } + } + + $final_uri .= strtok($_SERVER['REQUEST_URI'],"?"); + $cgi_params = '?'.strtok("?"); + // remove the ticket if present in the CGI parameters + $cgi_params = preg_replace('/&ticket=[^&]*/','',$cgi_params); + $cgi_params = preg_replace('/\?ticket=[^&;]*/','?',$cgi_params); + $cgi_params = preg_replace('/\?%26/','?',$cgi_params); + $cgi_params = preg_replace('/\?&/','?',$cgi_params); + $cgi_params = preg_replace('/\?$/','',$cgi_params); + $final_uri .= $cgi_params; + $this->setURL($final_uri); + } + phpCAS::traceEnd($this->_url); + return $this->_url; + } + + /** + * This method sets the URL of the current request + * + * @param $url url to set for service + * + * @private + */ + function setURL($url) + { + $this->_url = $url; + } + + // ######################################################################## + // AUTHENTICATION ERROR HANDLING + // ######################################################################## + /** + * This method is used to print the HTML output when the user was not authenticated. + * + * @param $failure the failure that occured + * @param $cas_url the URL the CAS server was asked for + * @param $no_response the response from the CAS server (other + * parameters are ignored if TRUE) + * @param $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if TRUE) + * @param $cas_response the response of the CAS server + * @param $err_code the error code given by the CAS server + * @param $err_msg the error message given by the CAS server + * + * @private + */ + function authError($failure,$cas_url,$no_response,$bad_response='',$cas_response='',$err_code='',$err_msg='') + { + phpCAS::traceBegin(); + + $this->printHTMLHeader($this->getString(CAS_STR_AUTHENTICATION_FAILED)); + printf($this->getString(CAS_STR_YOU_WERE_NOT_AUTHENTICATED),$this->getURL(),$_SERVER['SERVER_ADMIN']); + phpCAS::trace('CAS URL: '.$cas_url); + phpCAS::trace('Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace('Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace('Reason: bad response from the CAS server'); + } else { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace('Reason: CAS error'); + break; + case CAS_VERSION_2_0: + if ( empty($err_code) ) + phpCAS::trace('Reason: no CAS error'); + else + phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg); + break; + } + } + phpCAS::trace('CAS response: '.$cas_response); + } + $this->printHTMLFooter(); + phpCAS::traceExit(); + exit(); + } + + /** @} */ +} + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/domxml-php4-php5.php b/plugins/CasAuthentication/extlib/CAS/domxml-php4-php5.php index d64747514c..a0dfb99c7a 100644 --- a/plugins/CasAuthentication/extlib/CAS/domxml-php4-php5.php +++ b/plugins/CasAuthentication/extlib/CAS/domxml-php4-php5.php @@ -1,277 +1,277 @@ - - * { - * if (version_compare(PHP_VERSION,'5','>=')) - * require_once('domxml-php4-to-php5.php'); - * } - * - * - * Version 1.5.5, 2005-01-18, http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/ - * - * ------------------------------------------------------------------
- * Written by Alexandre Alapetite, http://alexandre.alapetite.net/cv/ - * - * Copyright 2004, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR), - * http://creativecommons.org/licenses/by-sa/2.0/fr/ - * http://alexandre.alapetite.net/divers/apropos/#by-sa - * - Attribution. You must give the original author credit - * - Share Alike. If you alter, transform, or build upon this work, - * you may distribute the resulting work only under a license identical to this one - * - The French law is authoritative - * - Any of these conditions can be waived if you get permission from Alexandre Alapetite - * - Please send to Alexandre Alapetite the modifications you make, - * in order to improve this file for the benefit of everybody - * - * If you want to distribute this code, please do it as a link to: - * http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/ - */ - -function domxml_new_doc($version) {return new php4DOMDocument('');} -function domxml_open_file($filename) {return new php4DOMDocument($filename);} -function domxml_open_mem($str) -{ - $dom=new php4DOMDocument(''); - $dom->myDOMNode->loadXML($str); - return $dom; -} -function xpath_eval($xpath_context,$eval_str,$contextnode=null) {return $xpath_context->query($eval_str,$contextnode);} -function xpath_new_context($dom_document) {return new php4DOMXPath($dom_document);} - -class php4DOMAttr extends php4DOMNode -{ - function php4DOMAttr($aDOMAttr) {$this->myDOMNode=$aDOMAttr;} - function Name() {return $this->myDOMNode->name;} - function Specified() {return $this->myDOMNode->specified;} - function Value() {return $this->myDOMNode->value;} -} - -class php4DOMDocument extends php4DOMNode -{ - function php4DOMDocument($filename='') - { - $this->myDOMNode=new DOMDocument(); - if ($filename!='') $this->myDOMNode->load($filename); - } - function create_attribute($name,$value) - { - $myAttr=$this->myDOMNode->createAttribute($name); - $myAttr->value=$value; - return new php4DOMAttr($myAttr,$this); - } - function create_cdata_section($content) {return new php4DOMNode($this->myDOMNode->createCDATASection($content),$this);} - function create_comment($data) {return new php4DOMNode($this->myDOMNode->createComment($data),$this);} - function create_element($name) {return new php4DOMElement($this->myDOMNode->createElement($name),$this);} - function create_text_node($content) {return new php4DOMNode($this->myDOMNode->createTextNode($content),$this);} - function document_element() {return new php4DOMElement($this->myDOMNode->documentElement,$this);} - function dump_file($filename,$compressionmode=false,$format=false) {return $this->myDOMNode->save($filename);} - function dump_mem($format=false,$encoding=false) {return $this->myDOMNode->saveXML();} - function get_element_by_id($id) {return new php4DOMElement($this->myDOMNode->getElementById($id),$this);} - function get_elements_by_tagname($name) - { - $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name); - $nodeSet=array(); - $i=0; - if (isset($myDOMNodeList)) - while ($node=$myDOMNodeList->item($i)) - { - $nodeSet[]=new php4DOMElement($node,$this); - $i++; - } - return $nodeSet; - } - function html_dump_mem() {return $this->myDOMNode->saveHTML();} - function root() {return new php4DOMElement($this->myDOMNode->documentElement,$this);} -} - -class php4DOMElement extends php4DOMNode -{ - function get_attribute($name) {return $this->myDOMNode->getAttribute($name);} - function get_elements_by_tagname($name) - { - $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name); - $nodeSet=array(); - $i=0; - if (isset($myDOMNodeList)) - while ($node=$myDOMNodeList->item($i)) - { - $nodeSet[]=new php4DOMElement($node,$this->myOwnerDocument); - $i++; - } - return $nodeSet; - } - function has_attribute($name) {return $this->myDOMNode->hasAttribute($name);} - function remove_attribute($name) {return $this->myDOMNode->removeAttribute($name);} - function set_attribute($name,$value) {return $this->myDOMNode->setAttribute($name,$value);} - function tagname() {return $this->myDOMNode->tagName;} -} - -class php4DOMNode -{ - var $myDOMNode; - var $myOwnerDocument; - function php4DOMNode($aDomNode,$aOwnerDocument) - { - $this->myDOMNode=$aDomNode; - $this->myOwnerDocument=$aOwnerDocument; - } - function __get($name) - { - if ($name=='type') return $this->myDOMNode->nodeType; - elseif ($name=='tagname') return $this->myDOMNode->tagName; - elseif ($name=='content') return $this->myDOMNode->textContent; - else - { - $myErrors=debug_backtrace(); - trigger_error('Undefined property: '.get_class($this).'::$'.$name.' ['.$myErrors[0]['file'].':'.$myErrors[0]['line'].']',E_USER_NOTICE); - return false; - } - } - function append_child($newnode) {return new php4DOMElement($this->myDOMNode->appendChild($newnode->myDOMNode),$this->myOwnerDocument);} - function append_sibling($newnode) {return new php4DOMElement($this->myDOMNode->parentNode->appendChild($newnode->myDOMNode),$this->myOwnerDocument);} - function attributes() - { - $myDOMNodeList=$this->myDOMNode->attributes; - $nodeSet=array(); - $i=0; - if (isset($myDOMNodeList)) - while ($node=$myDOMNodeList->item($i)) - { - $nodeSet[]=new php4DOMAttr($node,$this->myOwnerDocument); - $i++; - } - return $nodeSet; - } - function child_nodes() - { - $myDOMNodeList=$this->myDOMNode->childNodes; - $nodeSet=array(); - $i=0; - if (isset($myDOMNodeList)) - while ($node=$myDOMNodeList->item($i)) - { - $nodeSet[]=new php4DOMElement($node,$this->myOwnerDocument); - $i++; - } - return $nodeSet; - } - function children() {return $this->child_nodes();} - function clone_node($deep=false) {return new php4DOMElement($this->myDOMNode->cloneNode($deep),$this->myOwnerDocument);} - function first_child() {return new php4DOMElement($this->myDOMNode->firstChild,$this->myOwnerDocument);} - function get_content() {return $this->myDOMNode->textContent;} - function has_attributes() {return $this->myDOMNode->hasAttributes();} - function has_child_nodes() {return $this->myDOMNode->hasChildNodes();} - function insert_before($newnode,$refnode) {return new php4DOMElement($this->myDOMNode->insertBefore($newnode->myDOMNode,$refnode->myDOMNode),$this->myOwnerDocument);} - function is_blank_node() - { - $myDOMNodeList=$this->myDOMNode->childNodes; - $i=0; - if (isset($myDOMNodeList)) - while ($node=$myDOMNodeList->item($i)) - { - if (($node->nodeType==XML_ELEMENT_NODE)|| - (($node->nodeType==XML_TEXT_NODE)&&!ereg('^([[:cntrl:]]|[[:space:]])*$',$node->nodeValue))) - return false; - $i++; - } - return true; - } - function last_child() {return new php4DOMElement($this->myDOMNode->lastChild,$this->myOwnerDocument);} - function new_child($name,$content) - { - $mySubNode=$this->myDOMNode->ownerDocument->createElement($name); - $mySubNode->appendChild($this->myDOMNode->ownerDocument->createTextNode($content)); - $this->myDOMNode->appendChild($mySubNode); - return new php4DOMElement($mySubNode,$this->myOwnerDocument); - } - function next_sibling() {return new php4DOMElement($this->myDOMNode->nextSibling,$this->myOwnerDocument);} - function node_name() {return $this->myDOMNode->localName;} - function node_type() {return $this->myDOMNode->nodeType;} - function node_value() {return $this->myDOMNode->nodeValue;} - function owner_document() {return $this->myOwnerDocument;} - function parent_node() {return new php4DOMElement($this->myDOMNode->parentNode,$this->myOwnerDocument);} - function prefix() {return $this->myDOMNode->prefix;} - function previous_sibling() {return new php4DOMElement($this->myDOMNode->previousSibling,$this->myOwnerDocument);} - function remove_child($oldchild) {return new php4DOMElement($this->myDOMNode->removeChild($oldchild->myDOMNode),$this->myOwnerDocument);} - function replace_child($oldnode,$newnode) {return new php4DOMElement($this->myDOMNode->replaceChild($oldnode->myDOMNode,$newnode->myDOMNode),$this->myOwnerDocument);} - function set_content($text) - { - if (($this->myDOMNode->hasChildNodes())&&($this->myDOMNode->firstChild->nodeType==XML_TEXT_NODE)) - $this->myDOMNode->removeChild($this->myDOMNode->firstChild); - return $this->myDOMNode->appendChild($this->myDOMNode->ownerDocument->createTextNode($text)); - } -} - -class php4DOMNodelist -{ - var $myDOMNodelist; - var $nodeset; - function php4DOMNodelist($aDOMNodelist,$aOwnerDocument) - { - $this->myDOMNodelist=$aDOMNodelist; - $this->nodeset=array(); - $i=0; - if (isset($this->myDOMNodelist)) - while ($node=$this->myDOMNodelist->item($i)) - { - $this->nodeset[]=new php4DOMElement($node,$aOwnerDocument); - $i++; - } - } -} - -class php4DOMXPath -{ - var $myDOMXPath; - var $myOwnerDocument; - function php4DOMXPath($dom_document) - { - $this->myOwnerDocument=$dom_document; - $this->myDOMXPath=new DOMXPath($dom_document->myDOMNode); - } - function query($eval_str,$contextnode) - { - if (isset($contextnode)) return new php4DOMNodelist($this->myDOMXPath->query($eval_str,$contextnode->myDOMNode),$this->myOwnerDocument); - else return new php4DOMNodelist($this->myDOMXPath->query($eval_str),$this->myOwnerDocument); - } - function xpath_register_ns($prefix,$namespaceURI) {return $this->myDOMXPath->registerNamespace($prefix,$namespaceURI);} -} - -if (extension_loaded('xsl')) -{//See also: http://alexandre.alapetite.net/doc-alex/xslt-php4-php5/ - function domxml_xslt_stylesheet($xslstring) {return new php4DomXsltStylesheet(DOMDocument::loadXML($xslstring));} - function domxml_xslt_stylesheet_doc($dom_document) {return new php4DomXsltStylesheet($dom_document);} - function domxml_xslt_stylesheet_file($xslfile) {return new php4DomXsltStylesheet(DOMDocument::load($xslfile));} - class php4DomXsltStylesheet - { - var $myxsltProcessor; - function php4DomXsltStylesheet($dom_document) - { - $this->myxsltProcessor=new xsltProcessor(); - $this->myxsltProcessor->importStyleSheet($dom_document); - } - function process($dom_document,$xslt_parameters=array(),$param_is_xpath=false) - { - foreach ($xslt_parameters as $param=>$value) - $this->myxsltProcessor->setParameter('',$param,$value); - $myphp4DOMDocument=new php4DOMDocument(); - $myphp4DOMDocument->myDOMNode=$this->myxsltProcessor->transformToDoc($dom_document->myDOMNode); - return $myphp4DOMDocument; - } - function result_dump_file($dom_document,$filename) - { - $html=$dom_document->myDOMNode->saveHTML(); - file_put_contents($filename,$html); - return $html; - } - function result_dump_mem($dom_document) {return $dom_document->myDOMNode->saveHTML();} - } -} + + * { + * if (version_compare(PHP_VERSION,'5','>=')) + * require_once('domxml-php4-to-php5.php'); + * } + * + * + * Version 1.5.5, 2005-01-18, http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/ + * + * ------------------------------------------------------------------
+ * Written by Alexandre Alapetite, http://alexandre.alapetite.net/cv/ + * + * Copyright 2004, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR), + * http://creativecommons.org/licenses/by-sa/2.0/fr/ + * http://alexandre.alapetite.net/divers/apropos/#by-sa + * - Attribution. You must give the original author credit + * - Share Alike. If you alter, transform, or build upon this work, + * you may distribute the resulting work only under a license identical to this one + * - The French law is authoritative + * - Any of these conditions can be waived if you get permission from Alexandre Alapetite + * - Please send to Alexandre Alapetite the modifications you make, + * in order to improve this file for the benefit of everybody + * + * If you want to distribute this code, please do it as a link to: + * http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/ + */ + +function domxml_new_doc($version) {return new php4DOMDocument('');} +function domxml_open_file($filename) {return new php4DOMDocument($filename);} +function domxml_open_mem($str) +{ + $dom=new php4DOMDocument(''); + $dom->myDOMNode->loadXML($str); + return $dom; +} +function xpath_eval($xpath_context,$eval_str,$contextnode=null) {return $xpath_context->query($eval_str,$contextnode);} +function xpath_new_context($dom_document) {return new php4DOMXPath($dom_document);} + +class php4DOMAttr extends php4DOMNode +{ + function php4DOMAttr($aDOMAttr) {$this->myDOMNode=$aDOMAttr;} + function Name() {return $this->myDOMNode->name;} + function Specified() {return $this->myDOMNode->specified;} + function Value() {return $this->myDOMNode->value;} +} + +class php4DOMDocument extends php4DOMNode +{ + function php4DOMDocument($filename='') + { + $this->myDOMNode=new DOMDocument(); + if ($filename!='') $this->myDOMNode->load($filename); + } + function create_attribute($name,$value) + { + $myAttr=$this->myDOMNode->createAttribute($name); + $myAttr->value=$value; + return new php4DOMAttr($myAttr,$this); + } + function create_cdata_section($content) {return new php4DOMNode($this->myDOMNode->createCDATASection($content),$this);} + function create_comment($data) {return new php4DOMNode($this->myDOMNode->createComment($data),$this);} + function create_element($name) {return new php4DOMElement($this->myDOMNode->createElement($name),$this);} + function create_text_node($content) {return new php4DOMNode($this->myDOMNode->createTextNode($content),$this);} + function document_element() {return new php4DOMElement($this->myDOMNode->documentElement,$this);} + function dump_file($filename,$compressionmode=false,$format=false) {return $this->myDOMNode->save($filename);} + function dump_mem($format=false,$encoding=false) {return $this->myDOMNode->saveXML();} + function get_element_by_id($id) {return new php4DOMElement($this->myDOMNode->getElementById($id),$this);} + function get_elements_by_tagname($name) + { + $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name); + $nodeSet=array(); + $i=0; + if (isset($myDOMNodeList)) + while ($node=$myDOMNodeList->item($i)) + { + $nodeSet[]=new php4DOMElement($node,$this); + $i++; + } + return $nodeSet; + } + function html_dump_mem() {return $this->myDOMNode->saveHTML();} + function root() {return new php4DOMElement($this->myDOMNode->documentElement,$this);} +} + +class php4DOMElement extends php4DOMNode +{ + function get_attribute($name) {return $this->myDOMNode->getAttribute($name);} + function get_elements_by_tagname($name) + { + $myDOMNodeList=$this->myDOMNode->getElementsByTagName($name); + $nodeSet=array(); + $i=0; + if (isset($myDOMNodeList)) + while ($node=$myDOMNodeList->item($i)) + { + $nodeSet[]=new php4DOMElement($node,$this->myOwnerDocument); + $i++; + } + return $nodeSet; + } + function has_attribute($name) {return $this->myDOMNode->hasAttribute($name);} + function remove_attribute($name) {return $this->myDOMNode->removeAttribute($name);} + function set_attribute($name,$value) {return $this->myDOMNode->setAttribute($name,$value);} + function tagname() {return $this->myDOMNode->tagName;} +} + +class php4DOMNode +{ + var $myDOMNode; + var $myOwnerDocument; + function php4DOMNode($aDomNode,$aOwnerDocument) + { + $this->myDOMNode=$aDomNode; + $this->myOwnerDocument=$aOwnerDocument; + } + function __get($name) + { + if ($name=='type') return $this->myDOMNode->nodeType; + elseif ($name=='tagname') return $this->myDOMNode->tagName; + elseif ($name=='content') return $this->myDOMNode->textContent; + else + { + $myErrors=debug_backtrace(); + trigger_error('Undefined property: '.get_class($this).'::$'.$name.' ['.$myErrors[0]['file'].':'.$myErrors[0]['line'].']',E_USER_NOTICE); + return false; + } + } + function append_child($newnode) {return new php4DOMElement($this->myDOMNode->appendChild($newnode->myDOMNode),$this->myOwnerDocument);} + function append_sibling($newnode) {return new php4DOMElement($this->myDOMNode->parentNode->appendChild($newnode->myDOMNode),$this->myOwnerDocument);} + function attributes() + { + $myDOMNodeList=$this->myDOMNode->attributes; + $nodeSet=array(); + $i=0; + if (isset($myDOMNodeList)) + while ($node=$myDOMNodeList->item($i)) + { + $nodeSet[]=new php4DOMAttr($node,$this->myOwnerDocument); + $i++; + } + return $nodeSet; + } + function child_nodes() + { + $myDOMNodeList=$this->myDOMNode->childNodes; + $nodeSet=array(); + $i=0; + if (isset($myDOMNodeList)) + while ($node=$myDOMNodeList->item($i)) + { + $nodeSet[]=new php4DOMElement($node,$this->myOwnerDocument); + $i++; + } + return $nodeSet; + } + function children() {return $this->child_nodes();} + function clone_node($deep=false) {return new php4DOMElement($this->myDOMNode->cloneNode($deep),$this->myOwnerDocument);} + function first_child() {return new php4DOMElement($this->myDOMNode->firstChild,$this->myOwnerDocument);} + function get_content() {return $this->myDOMNode->textContent;} + function has_attributes() {return $this->myDOMNode->hasAttributes();} + function has_child_nodes() {return $this->myDOMNode->hasChildNodes();} + function insert_before($newnode,$refnode) {return new php4DOMElement($this->myDOMNode->insertBefore($newnode->myDOMNode,$refnode->myDOMNode),$this->myOwnerDocument);} + function is_blank_node() + { + $myDOMNodeList=$this->myDOMNode->childNodes; + $i=0; + if (isset($myDOMNodeList)) + while ($node=$myDOMNodeList->item($i)) + { + if (($node->nodeType==XML_ELEMENT_NODE)|| + (($node->nodeType==XML_TEXT_NODE)&&!ereg('^([[:cntrl:]]|[[:space:]])*$',$node->nodeValue))) + return false; + $i++; + } + return true; + } + function last_child() {return new php4DOMElement($this->myDOMNode->lastChild,$this->myOwnerDocument);} + function new_child($name,$content) + { + $mySubNode=$this->myDOMNode->ownerDocument->createElement($name); + $mySubNode->appendChild($this->myDOMNode->ownerDocument->createTextNode($content)); + $this->myDOMNode->appendChild($mySubNode); + return new php4DOMElement($mySubNode,$this->myOwnerDocument); + } + function next_sibling() {return new php4DOMElement($this->myDOMNode->nextSibling,$this->myOwnerDocument);} + function node_name() {return $this->myDOMNode->localName;} + function node_type() {return $this->myDOMNode->nodeType;} + function node_value() {return $this->myDOMNode->nodeValue;} + function owner_document() {return $this->myOwnerDocument;} + function parent_node() {return new php4DOMElement($this->myDOMNode->parentNode,$this->myOwnerDocument);} + function prefix() {return $this->myDOMNode->prefix;} + function previous_sibling() {return new php4DOMElement($this->myDOMNode->previousSibling,$this->myOwnerDocument);} + function remove_child($oldchild) {return new php4DOMElement($this->myDOMNode->removeChild($oldchild->myDOMNode),$this->myOwnerDocument);} + function replace_child($oldnode,$newnode) {return new php4DOMElement($this->myDOMNode->replaceChild($oldnode->myDOMNode,$newnode->myDOMNode),$this->myOwnerDocument);} + function set_content($text) + { + if (($this->myDOMNode->hasChildNodes())&&($this->myDOMNode->firstChild->nodeType==XML_TEXT_NODE)) + $this->myDOMNode->removeChild($this->myDOMNode->firstChild); + return $this->myDOMNode->appendChild($this->myDOMNode->ownerDocument->createTextNode($text)); + } +} + +class php4DOMNodelist +{ + var $myDOMNodelist; + var $nodeset; + function php4DOMNodelist($aDOMNodelist,$aOwnerDocument) + { + $this->myDOMNodelist=$aDOMNodelist; + $this->nodeset=array(); + $i=0; + if (isset($this->myDOMNodelist)) + while ($node=$this->myDOMNodelist->item($i)) + { + $this->nodeset[]=new php4DOMElement($node,$aOwnerDocument); + $i++; + } + } +} + +class php4DOMXPath +{ + var $myDOMXPath; + var $myOwnerDocument; + function php4DOMXPath($dom_document) + { + $this->myOwnerDocument=$dom_document; + $this->myDOMXPath=new DOMXPath($dom_document->myDOMNode); + } + function query($eval_str,$contextnode) + { + if (isset($contextnode)) return new php4DOMNodelist($this->myDOMXPath->query($eval_str,$contextnode->myDOMNode),$this->myOwnerDocument); + else return new php4DOMNodelist($this->myDOMXPath->query($eval_str),$this->myOwnerDocument); + } + function xpath_register_ns($prefix,$namespaceURI) {return $this->myDOMXPath->registerNamespace($prefix,$namespaceURI);} +} + +if (extension_loaded('xsl')) +{//See also: http://alexandre.alapetite.net/doc-alex/xslt-php4-php5/ + function domxml_xslt_stylesheet($xslstring) {return new php4DomXsltStylesheet(DOMDocument::loadXML($xslstring));} + function domxml_xslt_stylesheet_doc($dom_document) {return new php4DomXsltStylesheet($dom_document);} + function domxml_xslt_stylesheet_file($xslfile) {return new php4DomXsltStylesheet(DOMDocument::load($xslfile));} + class php4DomXsltStylesheet + { + var $myxsltProcessor; + function php4DomXsltStylesheet($dom_document) + { + $this->myxsltProcessor=new xsltProcessor(); + $this->myxsltProcessor->importStyleSheet($dom_document); + } + function process($dom_document,$xslt_parameters=array(),$param_is_xpath=false) + { + foreach ($xslt_parameters as $param=>$value) + $this->myxsltProcessor->setParameter('',$param,$value); + $myphp4DOMDocument=new php4DOMDocument(); + $myphp4DOMDocument->myDOMNode=$this->myxsltProcessor->transformToDoc($dom_document->myDOMNode); + return $myphp4DOMDocument; + } + function result_dump_file($dom_document,$filename) + { + $html=$dom_document->myDOMNode->saveHTML(); + file_put_contents($filename,$html); + return $html; + } + function result_dump_mem($dom_document) {return $dom_document->myDOMNode->saveHTML();} + } +} ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/catalan.php b/plugins/CasAuthentication/extlib/CAS/languages/catalan.php index 3d67473d98..0b139c7cad 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/catalan.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/catalan.php @@ -1,27 +1,27 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => 'usant servidor', - CAS_STR_AUTHENTICATION_WANTED - => 'Autentificació CAS necessària!', - CAS_STR_LOGOUT - => 'Sortida de CAS necessària!', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'Ja hauria d\ haver estat redireccionat al servidor CAS. Feu click aquí per a continuar.', - CAS_STR_AUTHENTICATION_FAILED - => 'Autentificació CAS fallida!', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

No estàs autentificat.

Pots tornar a intentar-ho fent click aquí.

Si el problema persisteix hauría de contactar amb l\'administrador d\'aquest llocc.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'El servei `%s\' no està disponible (%s).' -); - -?> + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => 'usant servidor', + CAS_STR_AUTHENTICATION_WANTED + => 'Autentificació CAS necessària!', + CAS_STR_LOGOUT + => 'Sortida de CAS necessària!', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'Ja hauria d\ haver estat redireccionat al servidor CAS. Feu click aquí per a continuar.', + CAS_STR_AUTHENTICATION_FAILED + => 'Autentificació CAS fallida!', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

No estàs autentificat.

Pots tornar a intentar-ho fent click aquí.

Si el problema persisteix hauría de contactar amb l\'administrador d\'aquest llocc.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'El servei `%s\' no està disponible (%s).' +); + +?> diff --git a/plugins/CasAuthentication/extlib/CAS/languages/english.php b/plugins/CasAuthentication/extlib/CAS/languages/english.php index c143450314..d38d42c1f7 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/english.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/english.php @@ -1,27 +1,27 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => 'using server', - CAS_STR_AUTHENTICATION_WANTED - => 'CAS Authentication wanted!', - CAS_STR_LOGOUT - => 'CAS logout wanted!', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'You should already have been redirected to the CAS server. Click here to continue.', - CAS_STR_AUTHENTICATION_FAILED - => 'CAS Authentication failed!', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

You were not authenticated.

You may submit your request again by clicking here.

If the problem persists, you may contact the administrator of this site.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'The service `%s\' is not available (%s).' -); - + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => 'using server', + CAS_STR_AUTHENTICATION_WANTED + => 'CAS Authentication wanted!', + CAS_STR_LOGOUT + => 'CAS logout wanted!', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'You should already have been redirected to the CAS server. Click here to continue.', + CAS_STR_AUTHENTICATION_FAILED + => 'CAS Authentication failed!', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

You were not authenticated.

You may submit your request again by clicking here.

If the problem persists, you may contact the administrator of this site.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'The service `%s\' is not available (%s).' +); + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/french.php b/plugins/CasAuthentication/extlib/CAS/languages/french.php index 675a7fc04e..32d1416850 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/french.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/french.php @@ -1,28 +1,28 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => 'utilisant le serveur', - CAS_STR_AUTHENTICATION_WANTED - => 'Authentication CAS nécessaire !', - CAS_STR_LOGOUT - => 'Déconnexion demandée !', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'Vous auriez du etre redirigé(e) vers le serveur CAS. Cliquez ici pour continuer.', - CAS_STR_AUTHENTICATION_FAILED - => 'Authentification CAS infructueuse !', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

Vous n\'avez pas été authentifié(e).

Vous pouvez soumettre votre requete à nouveau en cliquant ici.

Si le problème persiste, vous pouvez contacter l\'administrateur de ce site.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'Le service `%s\' est indisponible (%s)' - -); - + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => 'utilisant le serveur', + CAS_STR_AUTHENTICATION_WANTED + => 'Authentication CAS nécessaire !', + CAS_STR_LOGOUT + => 'Déconnexion demandée !', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'Vous auriez du etre redirigé(e) vers le serveur CAS. Cliquez ici pour continuer.', + CAS_STR_AUTHENTICATION_FAILED + => 'Authentification CAS infructueuse !', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

Vous n\'avez pas été authentifié(e).

Vous pouvez soumettre votre requete à nouveau en cliquant ici.

Si le problème persiste, vous pouvez contacter l\'administrateur de ce site.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'Le service `%s\' est indisponible (%s)' + +); + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/german.php b/plugins/CasAuthentication/extlib/CAS/languages/german.php index 29daeb35dd..55c3238fde 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/german.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/german.php @@ -1,27 +1,27 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => 'via Server', - CAS_STR_AUTHENTICATION_WANTED - => 'CAS Authentifizierung erforderlich!', - CAS_STR_LOGOUT - => 'CAS Abmeldung!', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'eigentlich häten Sie zum CAS Server weitergeleitet werden sollen. Drücken Sie hier um fortzufahren.', - CAS_STR_AUTHENTICATION_FAILED - => 'CAS Anmeldung fehlgeschlagen!', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

Sie wurden nicht angemeldet.

Um es erneut zu versuchen klicken Sie hier.

Wenn das Problem bestehen bleibt, kontkatieren Sie den Administrator dieser Seite.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'Der Dienst `%s\' ist nicht verfügbar (%s).' -); - + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => 'via Server', + CAS_STR_AUTHENTICATION_WANTED + => 'CAS Authentifizierung erforderlich!', + CAS_STR_LOGOUT + => 'CAS Abmeldung!', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'eigentlich häten Sie zum CAS Server weitergeleitet werden sollen. Drücken Sie hier um fortzufahren.', + CAS_STR_AUTHENTICATION_FAILED + => 'CAS Anmeldung fehlgeschlagen!', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

Sie wurden nicht angemeldet.

Um es erneut zu versuchen klicken Sie hier.

Wenn das Problem bestehen bleibt, kontkatieren Sie den Administrator dieser Seite.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'Der Dienst `%s\' ist nicht verfügbar (%s).' +); + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/greek.php b/plugins/CasAuthentication/extlib/CAS/languages/greek.php index c17b1d6637..d41bf783b4 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/greek.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/greek.php @@ -1,27 +1,27 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => '÷ñçóéìïðïéåßôáé ï åîõðçñåôçôÞò', - CAS_STR_AUTHENTICATION_WANTED - => 'Áðáéôåßôáé ç ôáõôïðïßçóç CAS!', - CAS_STR_LOGOUT - => 'Áðáéôåßôáé ç áðïóýíäåóç áðü CAS!', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'Èá Ýðñåðå íá åß÷áôå áíáêáôåõèõíèåß óôïí åîõðçñåôçôÞ CAS. ÊÜíôå êëßê åäþ ãéá íá óõíå÷ßóåôå.', - CAS_STR_AUTHENTICATION_FAILED - => 'Ç ôáõôïðïßçóç CAS áðÝôõ÷å!', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

Äåí ôáõôïðïéçèÞêáôå.

Ìðïñåßôå íá îáíáðñïóðáèÞóåôå, êÜíïíôáò êëßê åäþ.

Åáí ôï ðñüâëçìá åðéìåßíåé, åëÜôå óå åðáöÞ ìå ôïí äéá÷åéñéóôÞ.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'Ç õðçñåóßá `%s\' äåí åßíáé äéáèÝóéìç (%s).' -); - + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => '÷ñçóéìïðïéåßôáé ï åîõðçñåôçôÞò', + CAS_STR_AUTHENTICATION_WANTED + => 'Áðáéôåßôáé ç ôáõôïðïßçóç CAS!', + CAS_STR_LOGOUT + => 'Áðáéôåßôáé ç áðïóýíäåóç áðü CAS!', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'Èá Ýðñåðå íá åß÷áôå áíáêáôåõèõíèåß óôïí åîõðçñåôçôÞ CAS. ÊÜíôå êëßê åäþ ãéá íá óõíå÷ßóåôå.', + CAS_STR_AUTHENTICATION_FAILED + => 'Ç ôáõôïðïßçóç CAS áðÝôõ÷å!', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

Äåí ôáõôïðïéçèÞêáôå.

Ìðïñåßôå íá îáíáðñïóðáèÞóåôå, êÜíïíôáò êëßê åäþ.

Åáí ôï ðñüâëçìá åðéìåßíåé, åëÜôå óå åðáöÞ ìå ôïí äéá÷åéñéóôÞ.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'Ç õðçñåóßá `%s\' äåí åßíáé äéáèÝóéìç (%s).' +); + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/languages.php b/plugins/CasAuthentication/extlib/CAS/languages/languages.php index 2c6f8bb3b3..001cfe445c 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/languages.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/languages.php @@ -1,24 +1,24 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -//@{ -/** - * a phpCAS string index - */ -define("CAS_STR_USING_SERVER", 1); -define("CAS_STR_AUTHENTICATION_WANTED", 2); -define("CAS_STR_LOGOUT", 3); -define("CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED", 4); -define("CAS_STR_AUTHENTICATION_FAILED", 5); -define("CAS_STR_YOU_WERE_NOT_AUTHENTICATED", 6); -define("CAS_STR_SERVICE_UNAVAILABLE", 7); -//@} - + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +//@{ +/** + * a phpCAS string index + */ +define("CAS_STR_USING_SERVER", 1); +define("CAS_STR_AUTHENTICATION_WANTED", 2); +define("CAS_STR_LOGOUT", 3); +define("CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED", 4); +define("CAS_STR_AUTHENTICATION_FAILED", 5); +define("CAS_STR_YOU_WERE_NOT_AUTHENTICATED", 6); +define("CAS_STR_SERVICE_UNAVAILABLE", 7); +//@} + ?> \ No newline at end of file diff --git a/plugins/CasAuthentication/extlib/CAS/languages/spanish.php b/plugins/CasAuthentication/extlib/CAS/languages/spanish.php index 3a8ffc2535..04067ca03b 100644 --- a/plugins/CasAuthentication/extlib/CAS/languages/spanish.php +++ b/plugins/CasAuthentication/extlib/CAS/languages/spanish.php @@ -1,27 +1,27 @@ - - * @sa @link internalLang Internationalization @endlink - * @ingroup internalLang - */ - -$this->_strings = array( - CAS_STR_USING_SERVER - => 'usando servidor', - CAS_STR_AUTHENTICATION_WANTED - => '¡Autentificación CAS necesaria!', - CAS_STR_LOGOUT - => '¡Salida CAS necesaria!', - CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED - => 'Ya debería haber sido redireccionado al servidor CAS. Haga click aquí para continuar.', - CAS_STR_AUTHENTICATION_FAILED - => '¡Autentificación CAS fallida!', - CAS_STR_YOU_WERE_NOT_AUTHENTICATED - => '

No estás autentificado.

Puedes volver a intentarlo haciendo click aquí.

Si el problema persiste debería contactar con el administrador de este sitio.

', - CAS_STR_SERVICE_UNAVAILABLE - => 'El servicio `%s\' no está disponible (%s).' -); - -?> + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +$this->_strings = array( + CAS_STR_USING_SERVER + => 'usando servidor', + CAS_STR_AUTHENTICATION_WANTED + => '¡Autentificación CAS necesaria!', + CAS_STR_LOGOUT + => '¡Salida CAS necesaria!', + CAS_STR_SHOULD_HAVE_BEEN_REDIRECTED + => 'Ya debería haber sido redireccionado al servidor CAS. Haga click aquí para continuar.', + CAS_STR_AUTHENTICATION_FAILED + => '¡Autentificación CAS fallida!', + CAS_STR_YOU_WERE_NOT_AUTHENTICATED + => '

No estás autentificado.

Puedes volver a intentarlo haciendo click aquí.

Si el problema persiste debería contactar con el administrador de este sitio.

', + CAS_STR_SERVICE_UNAVAILABLE + => 'El servicio `%s\' no está disponible (%s).' +); + +?> From 04c76fc4e5d711ba38d22ffe201bb93d40126071 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 11 Jan 2010 16:23:34 -0800 Subject: [PATCH 27/57] safer storage for diskcacheplugin --- plugins/DiskCachePlugin.php | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/DiskCachePlugin.php b/plugins/DiskCachePlugin.php index 2b788decb9..b709ea3b31 100644 --- a/plugins/DiskCachePlugin.php +++ b/plugins/DiskCachePlugin.php @@ -68,9 +68,12 @@ class DiskCachePlugin extends Plugin function onStartCacheGet(&$key, &$value) { $filename = $this->keyToFilename($key); + if (file_exists($filename)) { $data = file_get_contents($filename); - $value = unserialize($data); + if ($data !== false) { + $value = unserialize($data); + } } Event::handle('EndCacheGet', array($key, &$value)); @@ -116,7 +119,24 @@ class DiskCachePlugin extends Plugin return false; } - file_put_contents($filename, serialize($value)); + // Write to a temp file and move to destination + + $tempname = tempnam(null, 'statusnetdiskcache'); + + $result = file_put_contents($tempname, serialize($value)); + + if ($result === false) { + $this->log(LOG_ERR, "Couldn't write '$key' to temp file '$tempname'"); + return false; + } + + $result = rename($tempname, $filename); + + if (!$result) { + $this->log(LOG_ERR, "Couldn't move temp file '$tempname' to path '$filename' for key '$key'"); + @unlink($tempname); + return false; + } Event::handle('EndCacheSet', array($key, $value, $flag, $expiry)); From 46b11070fe2f31561b89d933e15582fa2389e842 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 11 Jan 2010 18:31:49 -0800 Subject: [PATCH 28/57] add inbox to list of always-readwrite tables --- index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index 47cde87409..59805f6000 100644 --- a/index.php +++ b/index.php @@ -29,7 +29,7 @@ * @author Robin Millette * @author Sarven Capadisli * @author Tom Adams - * + * * @license GNU Affero General Public License http://www.gnu.org/licenses/ */ @@ -150,7 +150,7 @@ function checkMirror($action_obj, $args) { global $config; - static $alwaysRW = array('session', 'remember_me'); + static $alwaysRW = array('session', 'remember_me', 'inbox'); if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) { if (is_array(common_config('db', 'mirror'))) { From b9a487a3dab7e650fe21d0ab8a1db3e1b9c0ac3d Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Tue, 12 Jan 2010 03:06:31 +0000 Subject: [PATCH 29/57] Fixed width for form_notice selected view in IE --- theme/base/css/ie.css | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css index 4e50aadbe9..70a6fd11a5 100644 --- a/theme/base/css/ie.css +++ b/theme/base/css/ie.css @@ -19,10 +19,12 @@ display:block; width:17%; max-width:17%; } -.form_notice #notice_data-attach_selected { -width:78.5%; +.form_notice #notice_data-attach_selected, +.form_notice #notice_data-geo_selected { +width:78.75%; } -.form_notice #notice_data-attach_selected button { +.form_notice #notice_data-attach_selected button, +.form_notice #notice_data-geo_selected button { padding:0 4px; } .notice-options input.submit { From cbc3c7b141b8cfd8baa35a580e9fc9ae4f50343c Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 12 Jan 2010 07:24:43 -0800 Subject: [PATCH 30/57] Ticket 2152: avoid fatal when php.ini disables dl via disabled_functions (function_exists returns false even though it exists and cannot be redefined) --- lib/common.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/common.php b/lib/common.php index 7342c177a6..b280afec02 100644 --- a/lib/common.php +++ b/lib/common.php @@ -45,11 +45,20 @@ define('FOREIGN_FRIEND_RECV', 2); set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/'); -# To protect against upstream libraries which haven't updated -# for PHP 5.3 where dl() function may not be present... +// To protect against upstream libraries which haven't updated +// for PHP 5.3 where dl() function may not be present... if (!function_exists('dl')) { - function dl($library) { - return false; + // function_exists() returns false for things in disable_functions, + // but they still exist and we'll die if we try to redefine them. + // + // Fortunately trying to call the disabled one will only trigger + // a warning, not a fatal, so it's safe to leave it for our case. + // Callers will be suppressing warnings anyway. + $disabled = array_filter(array_map('trim', explode(',', ini_get('disable_functions')))); + if (!in_array('dl', $disabled)) { + function dl($library) { + return false; + } } } From 9c34d5c1073133992ef56ed02be3f53c5cefa379 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 12 Jan 2010 12:12:31 -0800 Subject: [PATCH 31/57] Actually skip the pingback if XML-RPC extension is missing, instead of considering it then trying anyway and dying of a fatal error. :) --- plugins/LinkbackPlugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/LinkbackPlugin.php b/plugins/LinkbackPlugin.php index 15e57ab0e8..8e44beae18 100644 --- a/plugins/LinkbackPlugin.php +++ b/plugins/LinkbackPlugin.php @@ -126,6 +126,7 @@ class LinkbackPlugin extends Plugin if (!extension_loaded('xmlrpc')) { if (!dl('xmlrpc.so')) { common_log(LOG_ERR, "Can't pingback; xmlrpc extension not available."); + return; } } From 4cc9b183d797cbd3d1f6230a46cb5df6e6161dbb Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 12 Jan 2010 12:20:45 -0800 Subject: [PATCH 32/57] Avoid spewing warnings about returning by reference on memcached cache misses by setting a var and returning it instead of trying to return false directly. This hacky workaround is brought to you by DB_DataObject's PHP 4 roots. --- classes/Memcached_DataObject.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index b68a4af8eb..9c9994ceac 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -98,14 +98,16 @@ class Memcached_DataObject extends DB_DataObject } else { $i = DB_DataObject::factory($cls); if (empty($i)) { - return false; + $i = false; + return $i; } $result = $i->get($k, $v); if ($result) { $i->encache(); return $i; } else { - return false; + $i = false; + return $i; } } } From 2b10e359fea9d6aabc5ab35557954a503bea730b Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 12 Jan 2010 12:26:08 -0800 Subject: [PATCH 33/57] Avoid PHP notice when outputting API data for remote users; no $user means no $user->timezone :) Trying to get property of non-object (/srv/com.leuksman.status/lib/api.php:171) --- lib/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api.php b/lib/api.php index d21851d503..707e4ac21a 100644 --- a/lib/api.php +++ b/lib/api.php @@ -168,7 +168,7 @@ class ApiAction extends Action $timezone = 'UTC'; - if ($user->timezone) { + if (!empty($user) && $user->timezone) { $timezone = $user->timezone; } From ec145b73fc91dd54695dd374c8a71a11e233b8c0 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 12 Jan 2010 19:57:15 -0800 Subject: [PATCH 34/57] Major refactoring of queue handlers to support running multiple sites in one daemon. Key changes: * Initialization code moved from common.php to StatusNet class; can now switch configurations during runtime. * As a consequence, configuration files must now be idempotent... Be careful with constant, function or class definitions. * Control structure for daemons/QueueManager/QueueHandler has been refactored; the run loop is now managed by IoMaster run via scripts/queuedaemon.php IoManager subclasses are woken to handle socket input or polling, and may cover multiple sites. * Plugins can implement notice queue handlers more easily by registering a QueueHandler class; no more need to add a daemon. The new QueueDaemon runs from scripts/queuedaemon.php: * This replaces most of the old *handler.php scripts; they've been refactored to the bare handler classes. * Spawns multiple child processes to spread load; defaults to CPU count on Linux and Mac OS X systems, or override with --threads=N * When multithreaded, child processes are automatically respawned on failure. * Threads gracefully shut down and restart when passing a soft memory limit (defaults to 90% of memory_limit), limiting damage from memory leaks. * Support for UDP-based monitoring: http://www.gitorious.org/snqmon Rough control flow diagram: QueueDaemon -> IoMaster -> IoManager QueueManager [listen or poll] -> QueueHandler XmppManager [ping & keepalive] XmppConfirmManager [poll updates] Todo: * Respawning features not currently available running single-threaded. * When running single-site, configuration changes aren't picked up. * New sites or config changes affecting queue subscriptions are not yet handled without a daemon restart. * SNMP monitoring output to integrate with general tools (nagios, ganglia) * Convert XMPP confirmation message sends to use stomp queue instead of polling * Convert xmppdaemon.php to IoManager? * Convert Twitter status, friends import polling daemons to IoManager * Clean up some error reporting and failure modes * May need to adjust queue priorities for best perf in backlog/flood cases Detailed code history available in my daemon-work branch: http://www.gitorious.org/~brion/statusnet/brion-fixes/commits/daemon-work --- classes/Memcached_DataObject.php | 23 ++ classes/Queue_item.php | 9 +- classes/Status_network.php | 23 +- lib/cache.php | 19 + lib/common.php | 194 +--------- lib/dbqueuemanager.php | 167 ++++---- lib/default.php | 2 + lib/event.php | 8 + lib/iomanager.php | 193 ++++++++++ lib/iomaster.php | 361 ++++++++++++++++++ lib/jabber.php | 25 +- {scripts => lib}/jabberqueuehandler.php | 49 +-- lib/liberalstomp.php | 133 +++++++ {scripts => lib}/ombqueuehandler.php | 57 +-- lib/pingqueuehandler.php | 37 ++ {scripts => lib}/pluginqueuehandler.php | 48 +-- {scripts => lib}/publicqueuehandler.php | 49 +-- lib/queuehandler.php | 100 ++--- lib/queuemanager.php | 149 +++++++- lib/queuemonitor.php | 116 ++++++ {scripts => lib}/smsqueuehandler.php | 48 +-- lib/statusnet.php | 295 ++++++++++++++ lib/stompqueuemanager.php | 312 ++++++++++----- lib/unqueuemanager.php | 58 +-- lib/util.php | 5 +- lib/xmppconfirmmanager.php | 168 ++++++++ lib/xmppmanager.php | 273 +++++++++++++ lib/xmppqueuehandler.php | 142 ------- plugins/Enjit/README | 5 + .../Enjit}/enjitqueuehandler.php | 44 +-- plugins/Facebook/FacebookPlugin.php | 46 +-- plugins/Facebook/facebookqueuehandler.php | 52 +-- plugins/MemcachePlugin.php | 17 + plugins/TwitterBridge/TwitterBridgePlugin.php | 50 +-- .../{daemons => }/twitterqueuehandler.php | 40 +- scripts/getvaliddaemons.php | 11 +- ...{pingqueuehandler.php => handlequeued.php} | 61 ++- scripts/queuedaemon.php | 265 +++++++++++++ scripts/xmppconfirmhandler.php | 161 -------- 39 files changed, 2612 insertions(+), 1203 deletions(-) create mode 100644 lib/iomanager.php create mode 100644 lib/iomaster.php rename {scripts => lib}/jabberqueuehandler.php (52%) mode change 100755 => 100644 create mode 100644 lib/liberalstomp.php rename {scripts => lib}/ombqueuehandler.php (57%) mode change 100755 => 100644 create mode 100644 lib/pingqueuehandler.php rename {scripts => lib}/pluginqueuehandler.php (61%) mode change 100755 => 100644 rename {scripts => lib}/publicqueuehandler.php (52%) mode change 100755 => 100644 create mode 100644 lib/queuemonitor.php rename {scripts => lib}/smsqueuehandler.php (54%) mode change 100755 => 100644 create mode 100644 lib/statusnet.php create mode 100644 lib/xmppconfirmmanager.php create mode 100644 lib/xmppmanager.php delete mode 100644 lib/xmppqueuehandler.php create mode 100644 plugins/Enjit/README rename {scripts => plugins/Enjit}/enjitqueuehandler.php (79%) mode change 100755 => 100644 mode change 100755 => 100644 plugins/Facebook/facebookqueuehandler.php rename plugins/TwitterBridge/{daemons => }/twitterqueuehandler.php (57%) mode change 100755 => 100644 rename scripts/{pingqueuehandler.php => handlequeued.php} (52%) create mode 100755 scripts/queuedaemon.php delete mode 100755 scripts/xmppconfirmhandler.php diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 9c9994ceac..4ecab9db62 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -331,6 +331,29 @@ class Memcached_DataObject extends DB_DataObject $exists = false; } + // @fixme horrible evil hack! + // + // In multisite configuration we don't want to keep around a separate + // connection for every database; we could end up with thousands of + // connections open per thread. In an ideal world we might keep + // a connection per server and select different databases, but that'd + // be reliant on having the same db username/pass as well. + // + // MySQL connections are cheap enough we're going to try just + // closing out the old connection and reopening when we encounter + // a new DSN. + // + // WARNING WARNING if we end up actually using multiple DBs at a time + // we'll need some fancier logic here. + if (!$exists && !empty($_DB_DATAOBJECT['CONNECTIONS'])) { + foreach ($_DB_DATAOBJECT['CONNECTIONS'] as $index => $conn) { + if (!empty($conn)) { + $conn->disconnect(); + } + unset($_DB_DATAOBJECT['CONNECTIONS'][$index]); + } + } + $result = parent::_connect(); if ($result && !$exists) { diff --git a/classes/Queue_item.php b/classes/Queue_item.php index 9c673540d7..cf805a6060 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -25,10 +25,12 @@ class Queue_item extends Memcached_DataObject function sequenceKey() { return array(false, false); } - static function top($transport) { + static function top($transport=null) { $qi = new Queue_item(); - $qi->transport = $transport; + if ($transport) { + $qi->transport = $transport; + } $qi->orderBy('created'); $qi->whereAdd('claimed is null'); @@ -40,7 +42,8 @@ class Queue_item extends Memcached_DataObject # XXX: potential race condition # can we force it to only update if claimed is still null # (or old)? - common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport); + common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . + ' for transport ' . $qi->transport); $orig = clone($qi); $qi->claimed = common_sql_now(); $result = $qi->update($orig); diff --git a/classes/Status_network.php b/classes/Status_network.php index 776f6abb03..ef8e1ed431 100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php @@ -49,6 +49,13 @@ class Status_network extends DB_DataObject static $cache = null; static $base = null; + /** + * @param string $dbhost + * @param string $dbuser + * @param string $dbpass + * @param string $dbname + * @param array $servers memcached servers to use for caching config info + */ static function setupDB($dbhost, $dbuser, $dbpass, $dbname, $servers) { global $config; @@ -60,12 +67,17 @@ class Status_network extends DB_DataObject if (class_exists('Memcache')) { self::$cache = new Memcache(); + // Can't close persistent connections, making forking painful. + // + // @fixme only do this in *parent* CLI processes. + // single-process and child-processes *should* use persistent. + $persist = php_sapi_name() != 'cli'; if (is_array($servers)) { foreach($servers as $server) { - self::$cache->addServer($server); + self::$cache->addServer($server, 11211, $persist); } } else { - self::$cache->addServer($servers); + self::$cache->addServer($servers, 11211, $persist); } } @@ -89,7 +101,7 @@ class Status_network extends DB_DataObject if (empty($sn)) { $sn = self::staticGet($k, $v); if (!empty($sn)) { - self::$cache->set($ck, $sn); + self::$cache->set($ck, clone($sn)); } } @@ -121,6 +133,11 @@ class Status_network extends DB_DataObject return parent::delete(); } + /** + * @param string $servername hostname + * @param string $pathname URL base path + * @param string $wildcard hostname suffix to match wildcard config + */ static function setupSite($servername, $pathname, $wildcard) { global $config; diff --git a/lib/cache.php b/lib/cache.php index b7b34c0500..635c96ad4c 100644 --- a/lib/cache.php +++ b/lib/cache.php @@ -179,4 +179,23 @@ class Cache return $success; } + + /** + * Close or reconnect any remote connections, such as to give + * daemon processes a chance to reconnect on a fresh socket. + * + * @return boolean success flag + */ + + function reconnect() + { + $success = false; + + if (Event::handle('StartCacheReconnect', array(&$success))) { + $success = true; + Event::handle('EndCacheReconnect', array()); + } + + return $success; + } } diff --git a/lib/common.php b/lib/common.php index b280afec02..61decebb7a 100644 --- a/lib/common.php +++ b/lib/common.php @@ -76,159 +76,14 @@ require_once(INSTALLDIR.'/lib/language.php'); require_once(INSTALLDIR.'/lib/event.php'); require_once(INSTALLDIR.'/lib/plugin.php'); -function _sn_to_path($sn) -{ - $past_root = substr($sn, 1); - $last_slash = strrpos($past_root, '/'); - if ($last_slash > 0) { - $p = substr($past_root, 0, $last_slash); - } else { - $p = ''; - } - return $p; -} - -// Save our sanity when code gets loaded through subroutines such as PHPUnit tests -global $default, $config, $_server, $_path; - -// try to figure out where we are. $server and $path -// can be set by including module, else we guess based -// on HTTP info. - -if (isset($server)) { - $_server = $server; -} else { - $_server = array_key_exists('SERVER_NAME', $_SERVER) ? - strtolower($_SERVER['SERVER_NAME']) : - null; -} - -if (isset($path)) { - $_path = $path; -} else { - $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ? - _sn_to_path($_SERVER['SCRIPT_NAME']) : - null; -} - -require_once(INSTALLDIR.'/lib/default.php'); - -// Set config values initially to default values - -$config = $default; - -// default configuration, overwritten in config.php - -$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); - -$config['db'] = $default['db']; - -// Backward compatibility - -$config['site']['design'] =& $config['design']; - -if (function_exists('date_default_timezone_set')) { - /* Work internally in UTC */ - date_default_timezone_set('UTC'); -} - function addPlugin($name, $attrs = null) { - $name = ucfirst($name); - $pluginclass = "{$name}Plugin"; - - if (!class_exists($pluginclass)) { - - $files = array("local/plugins/{$pluginclass}.php", - "local/plugins/{$name}/{$pluginclass}.php", - "local/{$pluginclass}.php", - "local/{$name}/{$pluginclass}.php", - "plugins/{$pluginclass}.php", - "plugins/{$name}/{$pluginclass}.php"); - - foreach ($files as $file) { - $fullpath = INSTALLDIR.'/'.$file; - if (@file_exists($fullpath)) { - include_once($fullpath); - break; - } - } - } - - $inst = new $pluginclass(); - - if (!empty($attrs)) { - foreach ($attrs as $aname => $avalue) { - $inst->$aname = $avalue; - } - } - return $inst; -} - -// From most general to most specific: -// server-wide, then vhost-wide, then for a path, -// finally for a dir (usually only need one of the last two). - -if (isset($conffile)) { - $_config_files = array($conffile); -} else { - $_config_files = array('/etc/statusnet/statusnet.php', - '/etc/statusnet/laconica.php', - '/etc/laconica/laconica.php', - '/etc/statusnet/'.$_server.'.php', - '/etc/laconica/'.$_server.'.php'); - - if (strlen($_path) > 0) { - $_config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php'; - $_config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php'; - } - - $_config_files[] = INSTALLDIR.'/config.php'; -} - -global $_have_a_config; -$_have_a_config = false; - -foreach ($_config_files as $_config_file) { - if (@file_exists($_config_file)) { - include_once($_config_file); - $_have_a_config = true; - } + return StatusNet::addPlugin($name, $attrs); } function _have_config() { - global $_have_a_config; - return $_have_a_config; -} - -// XXX: Throw a conniption if database not installed -// XXX: Find a way to use htmlwriter for this instead of handcoded markup -if (!_have_config()) { - echo '

'. _('No configuration file found. ') .'

'; - echo '

'. _('I looked for configuration files in the following places: ') .'
'. implode($_config_files, '
'); - echo '

'. _('You may wish to run the installer to fix this.') .'

'; - echo ''. _('Go to the installer.') .''; - exit; -} -// Fixup for statusnet.ini - -$_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1); - -if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'])) { - $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini'; -} - -// Backwards compatibility - -if (array_key_exists('memcached', $config)) { - if ($config['memcached']['enabled']) { - addPlugin('Memcache', array('servers' => $config['memcached']['server'])); - } - - if (!empty($config['memcached']['base'])) { - $config['cache']['base'] = $config['memcached']['base']; - } + return StatusNet::haveConfig(); } function __autoload($cls) @@ -247,27 +102,6 @@ function __autoload($cls) } } -// Load default plugins - -foreach ($config['plugins']['default'] as $name => $params) { - if (is_null($params)) { - addPlugin($name); - } else if (is_array($params)) { - if (count($params) == 0) { - addPlugin($name); - } else { - $keys = array_keys($params); - if (is_string($keys[0])) { - addPlugin($name, $params); - } else { - foreach ($params as $paramset) { - addPlugin($name, $paramset); - } - } - } - } -} - // XXX: how many of these could be auto-loaded on use? // XXX: note that these files should not use config options // at compile time since DB config options are not yet loaded. @@ -283,20 +117,20 @@ require_once INSTALLDIR.'/lib/subs.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; -// Load settings from database; note we need autoload for this - -Config::loadSettings(); - -// XXX: if plugins should check the schema at runtime, do that here. - -if ($config['db']['schemacheck'] == 'runtime') { - Event::handle('CheckSchema'); +try { + StatusNet::init(@$server, @$path, @$conffile); +} catch (NoConfigException $e) { + // XXX: Throw a conniption if database not installed + // XXX: Find a way to use htmlwriter for this instead of handcoded markup + echo '

'. _('No configuration file found. ') .'

'; + echo '

'. _('I looked for configuration files in the following places: ') .'
'; + echo implode($e->configFiles, '
'); + echo '

'. _('You may wish to run the installer to fix this.') .'

'; + echo ''. _('Go to the installer.') .''; + exit; } + // XXX: other formats here define('NICKNAME_FMT', VALIDATE_NUM.VALIDATE_ALPHA_LOWER); - -// Give plugins a chance to initialize in a fully-prepared environment - -Event::handle('InitializePlugin'); diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 750300928e..a5c6fd28b4 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -22,16 +22,20 @@ * @category QueueManager * @package StatusNet * @author Evan Prodromou - * @copyright 2009 StatusNet, Inc. + * @author Brion Vibber + * @copyright 2009-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/ */ class DBQueueManager extends QueueManager { - var $qis = array(); - - function enqueue($object, $queue) + /** + * Saves a notice object reference into the queue item table. + * @return boolean true on success + * @throws ServerException on failure + */ + public function enqueue($object, $queue) { $notice = $object; @@ -47,70 +51,95 @@ class DBQueueManager extends QueueManager throw new ServerException('DB error inserting queue item'); } + $this->stats('enqueued', $queue); + return true; } - function service($queue, $handler) + /** + * Poll every minute for new events during idle periods. + * We'll look in more often when there's data available. + * + * @return int seconds + */ + public function pollInterval() { - while (true) { - $this->_log(LOG_DEBUG, 'Checking for notices...'); - $timeout = $handler->timeout(); - $notice = $this->_nextItem($queue, $timeout); - if (empty($notice)) { - $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); - // Nothing in the queue. Do you - // have other tasks, like servicing your - // XMPP connection, to do? - $handler->idle(QUEUE_HANDLER_MISS_IDLE); - } else { - $this->_log(LOG_INFO, 'Got notice '. $notice->id); - // Yay! Got one! - if ($handler->handle_notice($notice)) { - $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id); - $this->_done($notice, $queue); - } else { - $this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id); - $this->_fail($notice, $queue); - } - // Chance to e.g. service your XMPP connection - $this->_log(LOG_DEBUG, 'Idling after success.'); - $handler->idle(QUEUE_HANDLER_HIT_IDLE); - } - // XXX: when do we give up? - } + return 60; } - function _nextItem($queue, $timeout=null) + /** + * Run a polling cycle during idle processing in the input loop. + * @return boolean true if we had a hit + */ + public function poll() + { + $this->_log(LOG_DEBUG, 'Checking for notices...'); + $item = $this->_nextItem(); + if ($item === false) { + $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); + return false; + } + if ($item === true) { + // We dequeued an entry for a deleted or invalid notice. + // Consider it a hit for poll rate purposes. + return true; + } + + list($queue, $notice) = $item; + $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue); + + // Yay! Got one! + $handler = $this->getHandler($queue); + if ($handler) { + if ($handler->handle_notice($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice"); + $this->_done($notice, $queue); + } else { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice"); + $this->_fail($notice, $queue); + } + } else { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue"); + $this->_fail($notice, $queue); + } + return true; + } + + /** + * Pop the oldest unclaimed item off the queue set and claim it. + * + * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice) + * giving the queue transport name. + */ + protected function _nextItem() { $start = time(); $result = null; - $sleeptime = 1; + $qi = Queue_item::top(); + if (empty($qi)) { + return false; + } - do { - $qi = Queue_item::top($queue); - if (empty($qi)) { - $this->_log(LOG_DEBUG, "No new queue items, sleeping $sleeptime seconds."); - sleep($sleeptime); - $sleeptime *= 2; - } else { - $notice = Notice::staticGet('id', $qi->notice_id); - if (!empty($notice)) { - $result = $notice; - } else { - $this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id); - $qi->delete(); - $qi->free(); - $qi = null; - } - $sleeptime = 1; - } - } while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout)); + $queue = $qi->transport; + $notice = Notice::staticGet('id', $qi->notice_id); + if (empty($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice"); + $qi->delete(); + return true; + } - return $result; + $result = $notice; + return array($queue, $notice); } - function _done($object, $queue) + /** + * Delete our claimed item from the queue after successful processing. + * + * @param Notice $object + * @param string $queue + */ + protected function _done($object, $queue) { // XXX: right now, we only handle notices @@ -120,24 +149,29 @@ class DBQueueManager extends QueueManager 'transport' => $queue)); if (empty($qi)) { - $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '. - 'for '.$notice->id.', queue '.$queue); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item"); } $qi->delete(); $qi->free(); - $qi = null; } - $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item"); + $this->stats('handled', $queue); $notice->free(); - $notice = null; } - function _fail($object, $queue) + /** + * Free our claimed queue item for later reprocessing in case of + * temporary failure. + * + * @param Notice $object + * @param string $queue + */ + protected function _fail($object, $queue) { // XXX: right now, we only handle notices @@ -147,11 +181,10 @@ class DBQueueManager extends QueueManager 'transport' => $queue)); if (empty($qi)) { - $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '. - 'for '.$notice->id.', queue '.$queue); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item"); } else { $orig = clone($qi); $qi->claimed = null; @@ -160,13 +193,13 @@ class DBQueueManager extends QueueManager } } - $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item"); + $this->stats('error', $queue); $notice->free(); - $notice = null; } - function _log($level, $msg) + protected function _log($level, $msg) { common_log($level, 'DBQueueManager: '.$msg); } diff --git a/lib/default.php b/lib/default.php index fa862f3ff1..f7f4777a2e 100644 --- a/lib/default.php +++ b/lib/default.php @@ -79,6 +79,8 @@ $default = 'queue_basename' => '/queue/statusnet/', 'stomp_username' => null, 'stomp_password' => null, + 'monitor' => null, // URL to monitor ping endpoint (work in progress) + 'softlimit' => '90%', // total size or % of memory_limit at which to restart queue threads gracefully ), 'license' => array('url' => 'http://creativecommons.org/licenses/by/3.0/', diff --git a/lib/event.php b/lib/event.php index 4819b71b4c..41fb53ffe9 100644 --- a/lib/event.php +++ b/lib/event.php @@ -138,4 +138,12 @@ class Event { } return false; } + + /** + * Disables any and all handlers that have been set up so far; + * use only if you know it's safe to reinitialize all plugins. + */ + public static function clearHandlers() { + Event::$_handlers = array(); + } } diff --git a/lib/iomanager.php b/lib/iomanager.php new file mode 100644 index 0000000000..ee2ff958b9 --- /dev/null +++ b/lib/iomanager.php @@ -0,0 +1,193 @@ +. + * + * @category QueueManager + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @author Brion Vibber + * @copyright 2009-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/ + */ + +abstract class IoManager +{ + const SINGLE_ONLY = 0; + const INSTANCE_PER_SITE = 1; + const INSTANCE_PER_PROCESS = 2; + + /** + * Factory function to get an appropriate subclass. + */ + public abstract static function get(); + + /** + * Tell the i/o queue master if and how we can handle multi-site + * processes. + * + * Return one of: + * IoManager::SINGLE_ONLY + * IoManager::INSTANCE_PER_SITE + * IoManager::INSTANCE_PER_PROCESS + */ + public static function multiSite() + { + return IoManager::SINGLE_ONLY; + } + + /** + * If in a multisite configuration, the i/o master will tell + * your manager about each site you'll have to handle so you + * can do any necessary per-site setup. + * + * @param string $site target site server name + */ + public function addSite($site) + { + /* no-op */ + } + + /** + * This method is called when data is available on one of your + * i/o manager's sockets. The socket with data is passed in, + * in case you have multiple sockets. + * + * If your i/o manager is based on polling during idle processing, + * you don't need to implement this. + * + * @param resource $socket + * @return boolean true on success, false on failure + */ + public function handleInput($socket) + { + return true; + } + + /** + * Return any open sockets that the run loop should listen + * for input on. If input comes in on a listed socket, + * the matching manager's handleInput method will be called. + * + * @return array of resources + */ + function getSockets() + { + return array(); + } + + /** + * Maximum planned time between poll() calls when input isn't waiting. + * Actual time may vary! + * + * When we get a polling hit, the timeout will be cut down to 0 while + * input is coming in, then will back off to this amount if no further + * input shows up. + * + * By default polling is disabled; you must override this to enable + * polling for this manager. + * + * @return int max poll interval in seconds, or 0 to disable polling + */ + function pollInterval() + { + return 0; + } + + /** + * Request a maximum timeout for listeners before the next idle period. + * Actual wait may be shorter, so don't go crazy in your idle()! + * Wait could be longer if other handlers performed some slow activity. + * + * Return 0 to request that listeners return immediately if there's no + * i/o and speed up the idle as much as possible; but don't do that all + * the time as this will burn CPU. + * + * @return int seconds + */ + function timeout() + { + return 60; + } + + /** + * Called by IoManager after each handled item or empty polling cycle. + * This is a good time to e.g. service your XMPP connection. + * + * Doesn't need to be overridden if there's no maintenance to do. + */ + function idle() + { + return true; + } + + /** + * The meat of a polling manager... check for something to do + * and do it! Note that you should not take too long, as other + * i/o managers may need to do some work too! + * + * On a successful hit, the next poll() call will come as soon + * as possible followed by exponential backoff up to pollInterval() + * if no more data is available. + * + * @return boolean true if events were hit + */ + public function poll() + { + return false; + } + + /** + * Initialization, run when the queue manager starts. + * If this function indicates failure, the handler run will be aborted. + * + * @param IoMaster $master process/event controller + * @return boolean true on success, false on failure + */ + public function start($master) + { + $this->master = $master; + return true; + } + + /** + * Cleanup, run when the queue manager ends. + * If this function indicates failure, a warning will be logged. + * + * @return boolean true on success, false on failure + */ + public function finish() + { + return true; + } + + /** + * Ping iomaster's queue status monitor with a stats update. + * Only valid during input loop! + * + * @param string $counter keyword for counter to increment + */ + public function stats($counter, $owners=array()) + { + $this->master->stats($counter, $owners); + } +} + diff --git a/lib/iomaster.php b/lib/iomaster.php new file mode 100644 index 0000000000..aff5b145c2 --- /dev/null +++ b/lib/iomaster.php @@ -0,0 +1,361 @@ +. + * + * @category QueueManager + * @package StatusNet + * @author Brion Vibber + * @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/ + */ + +class IoMaster +{ + public $id; + + protected $multiSite = false; + protected $managers = array(); + protected $singletons = array(); + + protected $pollTimeouts = array(); + protected $lastPoll = array(); + + /** + * @param string $id process ID to use in logging/monitoring + */ + public function __construct($id) + { + $this->id = $id; + $this->monitor = new QueueMonitor(); + } + + public function init($multiSite=null) + { + if ($multiSite !== null) { + $this->multiSite = $multiSite; + } + if ($this->multiSite) { + $this->sites = $this->findAllSites(); + } else { + $this->sites = array(common_config('site', 'server')); + } + + if (empty($this->sites)) { + throw new Exception("Empty status_network table, cannot init"); + } + + foreach ($this->sites as $site) { + if ($site != common_config('site', 'server')) { + StatusNet::init($site); + } + + $classes = array(); + if (Event::handle('StartIoManagerClasses', array(&$classes))) { + $classes[] = 'QueueManager'; + if (common_config('xmpp', 'enabled')) { + $classes[] = 'XmppManager'; // handles pings/reconnects + $classes[] = 'XmppConfirmManager'; // polls for outgoing confirmations + } + } + Event::handle('EndIoManagerClasses', array(&$classes)); + + foreach ($classes as $class) { + $this->instantiate($class); + } + } + } + + /** + * Pull all local sites from status_network table. + * @return array of hostnames + */ + protected function findAllSites() + { + $hosts = array(); + $sn = new Status_network(); + $sn->find(); + while ($sn->fetch()) { + $hosts[] = $sn->hostname; + } + return $hosts; + } + + /** + * Instantiate an i/o manager class for the current site. + * If a multi-site capable handler is already present, + * we don't need to build a new one. + * + * @param string $class + */ + protected function instantiate($class) + { + if (isset($this->singletons[$class])) { + // Already instantiated a multi-site-capable handler. + // Just let it know it should listen to this site too! + $this->singletons[$class]->addSite(common_config('site', 'server')); + return; + } + + $manager = $this->getManager($class); + + if ($this->multiSite) { + $caps = $manager->multiSite(); + if ($caps == IoManager::SINGLE_ONLY) { + throw new Exception("$class can't run with --all; aborting."); + } + if ($caps == IoManager::INSTANCE_PER_PROCESS) { + // Save this guy for later! + // We'll only need the one to cover multiple sites. + $this->singletons[$class] = $manager; + $manager->addSite(common_config('site', 'server')); + } + } + + $this->managers[] = $manager; + } + + protected function getManager($class) + { + return call_user_func(array($class, 'get')); + } + + /** + * Basic run loop... + * + * Initialize all io managers, then sit around waiting for input. + * Between events or timeouts, pass control back to idle() method + * to allow for any additional background processing. + */ + function service() + { + $this->logState('init'); + $this->start(); + + while (true) { + $timeouts = array_values($this->pollTimeouts); + $timeouts[] = 60; // default max timeout + + // Wait for something on one of our sockets + $sockets = array(); + $managers = array(); + foreach ($this->managers as $manager) { + foreach ($manager->getSockets() as $socket) { + $sockets[] = $socket; + $managers[] = $manager; + } + $timeouts[] = intval($manager->timeout()); + } + + $timeout = min($timeouts); + if ($sockets) { + $read = $sockets; + $write = array(); + $except = array(); + $this->logState('listening'); + common_log(LOG_INFO, "Waiting up to $timeout seconds for socket data..."); + $ready = stream_select($read, $write, $except, $timeout, 0); + + if ($ready === false) { + common_log(LOG_ERR, "Error selecting on sockets"); + } else if ($ready > 0) { + foreach ($read as $socket) { + $index = array_search($socket, $sockets, true); + if ($index !== false) { + $this->logState('queue'); + $managers[$index]->handleInput($socket); + } else { + common_log(LOG_ERR, "Saw input on a socket we didn't listen to"); + } + } + } + } + + if ($timeout > 0 && empty($sockets)) { + // If we had no listeners, sleep until the pollers' next requested wakeup. + common_log(LOG_INFO, "Sleeping $timeout seconds until next poll cycle..."); + $this->logState('sleep'); + sleep($timeout); + } + + $this->logState('poll'); + $this->poll(); + + $this->logState('idle'); + $this->idle(); + + $memoryLimit = $this->softMemoryLimit(); + if ($memoryLimit > 0) { + $usage = memory_get_usage(); + if ($usage > $memoryLimit) { + common_log(LOG_INFO, "Queue thread hit soft memory limit ($usage > $memoryLimit); gracefully restarting."); + break; + } + } + } + + $this->logState('shutdown'); + $this->finish(); + } + + /** + * Return fully-parsed soft memory limit in bytes. + * @return intval 0 or -1 if not set + */ + function softMemoryLimit() + { + $softLimit = trim(common_config('queue', 'softlimit')); + if (substr($softLimit, -1) == '%') { + $limit = trim(ini_get('memory_limit')); + $limit = $this->parseMemoryLimit($limit); + if ($limit > 0) { + return intval(substr($softLimit, 0, -1) * $limit / 100); + } else { + return -1; + } + } else { + return $this->parseMemoryLimit($limit); + } + return $softLimit; + } + + /** + * Interpret PHP shorthand for memory_limit and friends. + * Why don't they just expose the actual numeric value? :P + * @param string $mem + * @return int + */ + protected function parseMemoryLimit($mem) + { + // http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes + $size = array('k' => 1024, + 'm' => 1024*1024, + 'g' => 1024*1024*1024); + if (empty($mem)) { + return 0; + } else if (is_numeric($mem)) { + return intval($mem); + } else { + $mult = strtolower(substr($mem, -1)); + if (isset($size[$mult])) { + return substr($mem, 0, -1) * $size[$mult]; + } else { + return intval($mem); + } + } + } + + function start() + { + foreach ($this->managers as $index => $manager) { + $manager->start($this); + // @fixme error check + if ($manager->pollInterval()) { + // We'll want to check for input on the first pass + $this->pollTimeouts[$index] = 0; + $this->lastPoll[$index] = 0; + } + } + } + + function finish() + { + foreach ($this->managers as $manager) { + $manager->finish(); + // @fixme error check + } + } + + /** + * Called during the idle portion of the runloop to see which handlers + */ + function poll() + { + foreach ($this->managers as $index => $manager) { + $interval = $manager->pollInterval(); + if ($interval <= 0) { + // Not a polling manager. + continue; + } + + if (isset($this->pollTimeouts[$index])) { + $timeout = $this->pollTimeouts[$index]; + if (time() - $this->lastPoll[$index] < $timeout) { + // Not time to poll yet. + continue; + } + } else { + $timeout = 0; + } + $hit = $manager->poll(); + + $this->lastPoll[$index] = time(); + if ($hit) { + // Do the next poll quickly, there may be more input! + $this->pollTimeouts[$index] = 0; + } else { + // Empty queue. Exponential backoff up to the maximum poll interval. + if ($timeout > 0) { + $timeout = min($timeout * 2, $interval); + } else { + $timeout = 1; + } + $this->pollTimeouts[$index] = $timeout; + } + } + } + + /** + * Called after each handled item or empty polling cycle. + * This is a good time to e.g. service your XMPP connection. + */ + function idle() + { + foreach ($this->managers as $manager) { + $manager->idle(); + } + } + + /** + * Send thread state update to the monitoring server, if configured. + * + * @param string $state ('init', 'queue', 'shutdown' etc) + * @param string $substate (optional, eg queue name 'omb' 'sms' etc) + */ + protected function logState($state, $substate='') + { + $this->monitor->logState($this->id, $state, $substate); + } + + /** + * Send thread stats. + * Thread ID will be implicit; other owners can be listed as well + * for per-queue and per-site records. + * + * @param string $key counter name + * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01' + */ + public function stats($key, $owners=array()) + { + $owners[] = "thread:" . $this->id; + $this->monitor->stats($key, $owners); + } +} + diff --git a/lib/jabber.php b/lib/jabber.php index a821856a8b..1d0bb94231 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -86,7 +86,11 @@ class Sharing_XMPP extends XMPPHP_XMPP } /** - * connect the configured Jabber account to the configured server + * Lazy-connect the configured Jabber account to the configured server; + * if already opened, the same connection will be returned. + * + * In a multi-site background process, each site configuration + * will get its own connection. * * @param string $resource Resource to connect (defaults to configured resource) * @@ -95,16 +99,19 @@ class Sharing_XMPP extends XMPPHP_XMPP function jabber_connect($resource=null) { - static $conn = null; - if (!$conn) { + static $connections = array(); + $site = common_config('site', 'server'); + if (empty($connections[$site])) { + if (empty($resource)) { + $resource = common_config('xmpp', 'resource'); + } $conn = new Sharing_XMPP(common_config('xmpp', 'host') ? common_config('xmpp', 'host') : common_config('xmpp', 'server'), common_config('xmpp', 'port'), common_config('xmpp', 'user'), common_config('xmpp', 'password'), - ($resource) ? $resource : - common_config('xmpp', 'resource'), + $resource, common_config('xmpp', 'server'), common_config('xmpp', 'debug') ? true : false, @@ -115,12 +122,16 @@ function jabber_connect($resource=null) if (!$conn) { return false; } + $connections[$site] = $conn; $conn->autoSubscribe(); $conn->useEncryption(common_config('xmpp', 'encryption')); try { - $conn->connect(true); // true = persistent connection + common_log(LOG_INFO, __METHOD__ . ": connecting " . + common_config('xmpp', 'user') . '/' . $resource); + //$conn->connect(true); // true = persistent connection + $conn->connect(); // persistent connections break multisite } catch (XMPPHP_Exception $e) { common_log(LOG_ERR, $e->getMessage()); return false; @@ -128,7 +139,7 @@ function jabber_connect($resource=null) $conn->processUntil('session_start'); } - return $conn; + return $connections[$site]; } /** diff --git a/scripts/jabberqueuehandler.php b/lib/jabberqueuehandler.php old mode 100755 new mode 100644 similarity index 52% rename from scripts/jabberqueuehandler.php rename to lib/jabberqueuehandler.php index 8f3a56944d..b1518866d7 --- a/scripts/jabberqueuehandler.php +++ b/lib/jabberqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = << 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new JabberQueueHandler($id); - -$handler->runOnce(); diff --git a/lib/liberalstomp.php b/lib/liberalstomp.php new file mode 100644 index 0000000000..c9233843a4 --- /dev/null +++ b/lib/liberalstomp.php @@ -0,0 +1,133 @@ + + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class LiberalStomp extends Stomp +{ + /** + * We need to be able to get the socket so advanced daemons can + * do a select() waiting for input both from the queue and from + * other sources such as an XMPP connection. + * + * @return resource + */ + function getSocket() + { + return $this->_socket; + } + + /** + * Make socket connection to the server + * We also set the stream to non-blocking mode, since we'll be + * select'ing to wait for updates. In blocking mode it seems + * to get confused sometimes. + * + * @throws StompException + */ + protected function _makeConnection () + { + parent::_makeConnection(); + stream_set_blocking($this->_socket, 0); + } + + /** + * Version 1.0.0 of the Stomp library gets confused if messages + * come in too fast over the connection. This version will read + * out as many frames as are ready to be read from the socket. + * + * Modified from Stomp::readFrame() + * + * @return StompFrame False when no frame to read + */ + public function readFrames () + { + if (!$this->hasFrameToRead()) { + return false; + } + + $rb = 1024; + $data = ''; + $end = false; + $frames = array(); + + do { + // @fixme this sometimes hangs in blocking mode... + // shouldn't we have been idle until we found there's more data? + $read = fread($this->_socket, $rb); + if ($read === false) { + $this->_reconnect(); + // @fixme this will lose prior items + return $this->readFrames(); + } + $data .= $read; + if (strpos($data, "\x00") !== false) { + // Frames are null-delimited, but some servers + // may append an extra \n according to old bug reports. + $data = str_replace("\x00\n", "\x00", $data); + $chunks = explode("\x00", $data); + + $data = array_pop($chunks); + $frames = array_merge($frames, $chunks); + if ($data == '') { + // We're at the end of a frame; stop reading. + break; + } else { + // In the middle of a frame; keep going. + } + } + // @fixme find out why this len < 2 check was there + //$len = strlen($data); + } while (true);//$len < 2 || $end == false); + + return array_map(array($this, 'parseFrame'), $frames); + } + + /** + * Parse a raw Stomp frame into an object. + * Extracted from Stomp::readFrame() + * + * @param string $data + * @return StompFrame + */ + function parseFrame($data) + { + list ($header, $body) = explode("\n\n", $data, 2); + $header = explode("\n", $header); + $headers = array(); + $command = null; + foreach ($header as $v) { + if (isset($command)) { + list ($name, $value) = explode(':', $v, 2); + $headers[$name] = $value; + } else { + $command = $v; + } + } + $frame = new StompFrame($command, $headers, trim($body)); + if (isset($frame->headers['transformation']) && $frame->headers['transformation'] == 'jms-map-json') { + require_once 'Stomp/Message/Map.php'; + return new StompMessageMap($frame); + } else { + return $frame; + } + return $frame; + } +} + diff --git a/scripts/ombqueuehandler.php b/lib/ombqueuehandler.php old mode 100755 new mode 100644 similarity index 57% rename from scripts/ombqueuehandler.php rename to lib/ombqueuehandler.php index be33b98218..3ffc1313bc --- a/scripts/ombqueuehandler.php +++ b/lib/ombqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - + /** + * @fixme doesn't currently report failure back to the queue manager + * because omb_broadcast_notice() doesn't report it to us + */ function handle_notice($notice) { if ($this->is_remote($notice)) { $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); return true; } else { - return omb_broadcast_notice($notice); + require_once(INSTALLDIR.'/lib/omb.php'); + omb_broadcast_notice($notice); + return true; } } - function finish() - { - } - function is_remote($notice) { $user = User::staticGet($notice->profile_id); return is_null($user); } } - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new OmbQueueHandler($id); - -$handler->runOnce(); diff --git a/lib/pingqueuehandler.php b/lib/pingqueuehandler.php new file mode 100644 index 0000000000..8bb2180786 --- /dev/null +++ b/lib/pingqueuehandler.php @@ -0,0 +1,37 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Queue handler for pushing new notices to ping servers. + */ +class PingQueueHandler extends QueueHandler { + + function transport() { + return 'ping'; + } + + function handle_notice($notice) { + require_once INSTALLDIR . '/lib/ping.php'; + return ping_broadcast_notice($notice); + } +} diff --git a/scripts/pluginqueuehandler.php b/lib/pluginqueuehandler.php old mode 100755 new mode 100644 similarity index 61% rename from scripts/pluginqueuehandler.php rename to lib/pluginqueuehandler.php index fa39bdda6a..24d5046997 --- a/scripts/pluginqueuehandler.php +++ b/lib/pluginqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - function handle_notice($notice) { Event::handle('HandleQueuedNotice', array(&$notice)); return true; } } - -if (have_option('i', 'id')) { - $id = get_option_value('i', 'id'); -} else { - $id = null; -} - -$handler = new PluginQueueHandler($id); -$handler->runOnce(); diff --git a/scripts/publicqueuehandler.php b/lib/publicqueuehandler.php old mode 100755 new mode 100644 similarity index 52% rename from scripts/publicqueuehandler.php rename to lib/publicqueuehandler.php index 50a11bcba0..9ea9ee73a3 --- a/scripts/publicqueuehandler.php +++ b/lib/publicqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); die($e->getMessage()); } + return true; } } - -// Abort immediately if xmpp is not enabled, otherwise the daemon chews up -// lots of CPU trying to connect to unconfigured servers -if (common_config('xmpp','enabled')==false) { - print "Aborting daemon - xmpp is disabled\n"; - exit(); -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new PublicQueueHandler($id); - -$handler->runOnce(); diff --git a/lib/queuehandler.php b/lib/queuehandler.php index cd43b1e09a..613be6e330 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -19,14 +19,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/lib/daemon.php'); -require_once(INSTALLDIR.'/classes/Queue_item.php'); -require_once(INSTALLDIR.'/classes/Notice.php'); - -define('CLAIM_TIMEOUT', 1200); -define('QUEUE_HANDLER_MISS_IDLE', 10); -define('QUEUE_HANDLER_HIT_IDLE', 0); - /** * Base class for queue handlers. * @@ -36,24 +28,20 @@ define('QUEUE_HANDLER_HIT_IDLE', 0); * * Subclasses must override at least the following methods: * - transport - * - start - * - finish * - handle_notice - * - * Some subclasses will also want to override the idle handler: - * - idle */ -class QueueHandler extends Daemon +#class QueueHandler extends Daemon +class QueueHandler { - function __construct($id=null, $daemonize=true) - { - parent::__construct($daemonize); - - if ($id) { - $this->set_id($id); - } - } +# function __construct($id=null, $daemonize=true) +# { +# parent::__construct($daemonize); +# +# if ($id) { +# $this->set_id($id); +# } +# } /** * How many seconds a polling-based queue manager should wait between @@ -61,22 +49,23 @@ class QueueHandler extends Daemon * * Defaults to 60 seconds; override to speed up or slow down. * + * @fixme not really compatible with global queue manager * @return int timeout in seconds */ - function timeout() - { - return 60; - } +# function timeout() +# { +# return 60; +# } - function class_name() - { - return ucfirst($this->transport()) . 'Handler'; - } +# function class_name() +# { +# return ucfirst($this->transport()) . 'Handler'; +# } - function name() - { - return strtolower($this->class_name().'.'.$this->get_id()); - } +# function name() +# { +# return strtolower($this->class_name().'.'.$this->get_id()); +# } /** * Return transport keyword which identifies items this queue handler @@ -92,30 +81,6 @@ class QueueHandler extends Daemon return null; } - /** - * Initialization, run when the queue handler starts. - * If this function indicates failure, the handler run will be aborted. - * - * @fixme run() will abort if this doesn't return true, - * but some subclasses don't bother. - * @return boolean true on success, false on failure - */ - function start() - { - } - - /** - * Cleanup, run when the queue handler ends. - * If this function indicates failure, a warning will be logged. - * - * @fixme run() will throw warnings if this doesn't return true, - * but many subclasses don't bother. - * @return boolean true on success, false on failure - */ - function finish() - { - } - /** * Here's the meat of your queue handler -- you're handed a Notice * object, which you may do as you will with. @@ -169,29 +134,10 @@ class QueueHandler extends Daemon return true; } - /** - * Called by QueueHandler after each handled item or empty polling cycle. - * This is a good time to e.g. service your XMPP connection. - * - * Doesn't need to be overridden if there's no maintenance to do. - * - * @param int $timeout seconds to sleep if there's nothing to do - */ - function idle($timeout=0) - { - if ($timeout > 0) { - sleep($timeout); - } - } function log($level, $msg) { common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); } - - function getSockets() - { - return array(); - } } diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 43105b7a86..a98c0efffb 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Abstract class for queue managers + * Abstract class for i/o managers * * PHP version 5 * @@ -23,16 +23,32 @@ * @package StatusNet * @author Evan Prodromou * @author Sarven Capadisli - * @copyright 2009 StatusNet, Inc. + * @author Brion Vibber + * @copyright 2009-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/ */ -class QueueManager +/** + * Completed child classes must implement the enqueue() method. + * + * For background processing, classes should implement either socket-based + * input (handleInput(), getSockets()) or idle-loop polling (idle()). + */ +abstract class QueueManager extends IoManager { static $qm = null; - static function get() + /** + * Factory function to pull the appropriate QueueManager object + * for this site's configuration. It can then be used to queue + * events for later processing or to spawn a processing loop. + * + * Plugins can add to the built-in types by hooking StartNewQueueManager. + * + * @return QueueManager + */ + public static function get() { if (empty(self::$qm)) { @@ -62,13 +78,130 @@ class QueueManager return self::$qm; } - function enqueue($object, $queue) + /** + * @fixme wouldn't necessarily work with other class types. + * Better to change the interface...? + */ + public static function multiSite() { - throw ServerException("Unimplemented function 'enqueue' called"); + if (common_config('queue', 'subsystem') == 'stomp') { + return IoManager::INSTANCE_PER_PROCESS; + } else { + return IoManager::SINGLE_ONLY; + } } - function service($queue, $handler) + function __construct() { - throw ServerException("Unimplemented function 'service' called"); + $this->initialize(); + } + + /** + * Store an object (usually/always a Notice) into the given queue + * for later processing. No guarantee is made on when it will be + * processed; it could be immediately or at some unspecified point + * in the future. + * + * Must be implemented by any queue manager. + * + * @param Notice $object + * @param string $queue + */ + abstract function enqueue($object, $queue); + + /** + * Instantiate the appropriate QueueHandler class for the given queue. + * + * @param string $queue + * @return mixed QueueHandler or null + */ + function getHandler($queue) + { + if (isset($this->handlers[$queue])) { + $class = $this->handlers[$queue]; + if (class_exists($class)) { + return new $class(); + } else { + common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'"); + } + } else { + common_log(LOG_ERR, "Requested handler for unkown queue '$queue'"); + } + return null; + } + + /** + * Get a list of all registered queue transport names. + * + * @return array of strings + */ + function getQueues() + { + return array_keys($this->handlers); + } + + /** + * Initialize the list of queue handlers + * + * @event StartInitializeQueueManager + * @event EndInitializeQueueManager + */ + function initialize() + { + if (Event::handle('StartInitializeQueueManager', array($this))) { + $this->connect('plugin', 'PluginQueueHandler'); + $this->connect('omb', 'OmbQueueHandler'); + $this->connect('ping', 'PingQueueHandler'); + if (common_config('sms', 'enabled')) { + $this->connect('sms', 'SmsQueueHandler'); + } + + // XMPP output handlers... + if (common_config('xmpp', 'enabled')) { + $this->connect('jabber', 'JabberQueueHandler'); + $this->connect('public', 'PublicQueueHandler'); + + // @fixme this should move up a level or should get an actual queue + $this->connect('confirm', 'XmppConfirmHandler'); + } + + // For compat with old plugins not registering their own handlers. + $this->connect('plugin', 'PluginQueueHandler'); + } + Event::handle('EndInitializeQueueManager', array($this)); + } + + /** + * Register a queue transport name and handler class for your plugin. + * Only registered transports will be reliably picked up! + * + * @param string $transport + * @param string $class + */ + public function connect($transport, $class) + { + $this->handlers[$transport] = $class; + } + + /** + * Send a statistic ping to the queue monitoring system, + * optionally with a per-queue id. + * + * @param string $key + * @param string $queue + */ + function stats($key, $queue=false) + { + $owners = array(); + if ($queue) { + $owners[] = "queue:$queue"; + $owners[] = "site:" . common_config('site', 'server'); + } + if (isset($this->master)) { + $this->master->stats($key, $owners); + } else { + $monitor = new QueueMonitor(); + $monitor->stats($key, $owners); + } } } diff --git a/lib/queuemonitor.php b/lib/queuemonitor.php new file mode 100644 index 0000000000..1c306a6298 --- /dev/null +++ b/lib/queuemonitor.php @@ -0,0 +1,116 @@ +. + * + * @category QueueManager + * @package StatusNet + * @author Brion Vibber + * @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/ + */ + +class QueueMonitor +{ + protected $monSocket = null; + + /** + * Increment monitoring statistics for a given counter, if configured. + * Only explicitly listed thread/site/queue owners will be incremented. + * + * @param string $key counter name + * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01' + */ + public function stats($key, $owners=array()) + { + $this->ping(array('counter' => $key, + 'owners' => $owners)); + } + + /** + * Send thread state update to the monitoring server, if configured. + * + * @param string $thread ID (eg 'generic.1') + * @param string $state ('init', 'queue', 'shutdown' etc) + * @param string $substate (optional, eg queue name 'omb' 'sms' etc) + */ + public function logState($threadId, $state, $substate='') + { + $this->ping(array('thread_id' => $threadId, + 'state' => $state, + 'substate' => $substate, + 'ts' => microtime(true))); + } + + /** + * General call to the monitoring server + */ + protected function ping($data) + { + $target = common_config('queue', 'monitor'); + if (empty($target)) { + return; + } + + $data = $this->prepMonitorData($data); + + if (substr($target, 0, 4) == 'udp:') { + $this->pingUdp($target, $data); + } else if (substr($target, 0, 5) == 'http:') { + $this->pingHttp($target, $data); + } else { + common_log(LOG_ERR, __METHOD__ . ' unknown monitor target type ' . $target); + } + } + + protected function pingUdp($target, $data) + { + if (!$this->monSocket) { + $this->monSocket = stream_socket_client($target, $errno, $errstr); + } + if ($this->monSocket) { + $post = http_build_query($data, '', '&'); + stream_socket_sendto($this->monSocket, $post); + } else { + common_log(LOG_ERR, __METHOD__ . " UDP logging fail: $errstr"); + } + } + + protected function pingHttp($target, $data) + { + $client = new HTTPClient(); + $result = $client->post($target, array(), $data); + + if (!$result->isOk()) { + common_log(LOG_ERR, __METHOD__ . ' HTTP ' . $result->getStatus() . + ': ' . $result->getBody()); + } + } + + protected function prepMonitorData($data) + { + #asort($data); + #$macdata = http_build_query($data, '', '&'); + #$key = 'This is a nice old key'; + #$data['hmac'] = hash_hmac('sha256', $macdata, $key); + return $data; + } + +} diff --git a/scripts/smsqueuehandler.php b/lib/smsqueuehandler.php old mode 100755 new mode 100644 similarity index 54% rename from scripts/smsqueuehandler.php rename to lib/smsqueuehandler.php index 6583a77da8..48a96409d0 --- a/scripts/smsqueuehandler.php +++ b/lib/smsqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - function handle_notice($notice) { + require_once(INSTALLDIR.'/lib/mail.php'); return mail_broadcast_notice_sms($notice); } - - function finish() - { - } } - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new SmsQueueHandler($id); - -$handler->runOnce(); diff --git a/lib/statusnet.php b/lib/statusnet.php new file mode 100644 index 0000000000..0c5807d7b9 --- /dev/null +++ b/lib/statusnet.php @@ -0,0 +1,295 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +global $config, $_server, $_path; + +/** + * Global configuration setup and management. + */ +class StatusNet +{ + protected static $have_config; + + /** + * Configure and instantiate a plugin into the current configuration. + * Class definitions will be loaded from standard paths if necessary. + * Note that initialization events won't be fired until later. + * + * @param string $name class name & plugin file/subdir name + * @param array $attrs key/value pairs of public attributes to set on plugin instance + * + * @throws ServerException if plugin can't be found + */ + public static function addPlugin($name, $attrs = null) + { + $name = ucfirst($name); + $pluginclass = "{$name}Plugin"; + + if (!class_exists($pluginclass)) { + + $files = array("local/plugins/{$pluginclass}.php", + "local/plugins/{$name}/{$pluginclass}.php", + "local/{$pluginclass}.php", + "local/{$name}/{$pluginclass}.php", + "plugins/{$pluginclass}.php", + "plugins/{$name}/{$pluginclass}.php"); + + foreach ($files as $file) { + $fullpath = INSTALLDIR.'/'.$file; + if (@file_exists($fullpath)) { + include_once($fullpath); + break; + } + } + if (!class_exists($pluginclass)) { + throw new ServerException(500, "Plugin $name not found."); + } + } + + $inst = new $pluginclass(); + if (!empty($attrs)) { + foreach ($attrs as $aname => $avalue) { + $inst->$aname = $avalue; + } + } + return true; + } + + /** + * Initialize, or re-initialize, StatusNet global configuration + * and plugins. + * + * If switching site configurations during script execution, be + * careful when working with leftover objects -- global settings + * affect many things and they may not behave as you expected. + * + * @param $server optional web server hostname for picking config + * @param $path optional URL path for picking config + * @param $conffile optional configuration file path + * + * @throws NoConfigException if config file can't be found + */ + public static function init($server=null, $path=null, $conffile=null) + { + StatusNet::initDefaults($server, $path); + StatusNet::loadConfigFile($conffile); + + // Load settings from database; note we need autoload for this + Config::loadSettings(); + + self::initPlugins(); + } + + /** + * Fire initialization events for all instantiated plugins. + */ + protected static function initPlugins() + { + // Load default plugins + foreach (common_config('plugins', 'default') as $name => $params) { + if (is_null($params)) { + addPlugin($name); + } else if (is_array($params)) { + if (count($params) == 0) { + addPlugin($name); + } else { + $keys = array_keys($params); + if (is_string($keys[0])) { + addPlugin($name, $params); + } else { + foreach ($params as $paramset) { + addPlugin($name, $paramset); + } + } + } + } + } + + // XXX: if plugins should check the schema at runtime, do that here. + if (common_config('db', 'schemacheck') == 'runtime') { + Event::handle('CheckSchema'); + } + + // Give plugins a chance to initialize in a fully-prepared environment + Event::handle('InitializePlugin'); + } + + /** + * Quick-check if configuration has been established. + * Useful for functions which may get used partway through + * initialization to back off from fancier things. + * + * @return bool + */ + public function haveConfig() + { + return self::$have_config; + } + + /** + * Build default configuration array + * @return array + */ + protected static function defaultConfig() + { + global $_server, $_path; + require(INSTALLDIR.'/lib/default.php'); + return $default; + } + + /** + * Establish default configuration based on given or default server and path + * Sets global $_server, $_path, and $config + */ + protected static function initDefaults($server, $path) + { + global $_server, $_path, $config; + + Event::clearHandlers(); + + // try to figure out where we are. $server and $path + // can be set by including module, else we guess based + // on HTTP info. + + if (isset($server)) { + $_server = $server; + } else { + $_server = array_key_exists('SERVER_NAME', $_SERVER) ? + strtolower($_SERVER['SERVER_NAME']) : + null; + } + + if (isset($path)) { + $_path = $path; + } else { + $_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ? + self::_sn_to_path($_SERVER['SCRIPT_NAME']) : + null; + } + + // Set config values initially to default values + $default = self::defaultConfig(); + $config = $default; + + // default configuration, overwritten in config.php + // Keep DB_DataObject's db config synced to ours... + + $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); + + $config['db'] = $default['db']; + + // Backward compatibility + + $config['site']['design'] =& $config['design']; + + if (function_exists('date_default_timezone_set')) { + /* Work internally in UTC */ + date_default_timezone_set('UTC'); + } + } + + protected function _sn_to_path($sn) + { + $past_root = substr($sn, 1); + $last_slash = strrpos($past_root, '/'); + if ($last_slash > 0) { + $p = substr($past_root, 0, $last_slash); + } else { + $p = ''; + } + return $p; + } + + /** + * Load the default or specified configuration file. + * Modifies global $config and may establish plugins. + * + * @throws NoConfigException + */ + protected function loadConfigFile($conffile=null) + { + global $_server, $_path, $config; + + // From most general to most specific: + // server-wide, then vhost-wide, then for a path, + // finally for a dir (usually only need one of the last two). + + if (isset($conffile)) { + $config_files = array($conffile); + } else { + $config_files = array('/etc/statusnet/statusnet.php', + '/etc/statusnet/laconica.php', + '/etc/laconica/laconica.php', + '/etc/statusnet/'.$_server.'.php', + '/etc/laconica/'.$_server.'.php'); + + if (strlen($_path) > 0) { + $config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php'; + $config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php'; + } + + $config_files[] = INSTALLDIR.'/config.php'; + } + + self::$have_config = false; + + foreach ($config_files as $_config_file) { + if (@file_exists($_config_file)) { + include($_config_file); + self::$have_config = true; + } + } + + if (!self::$have_config) { + throw new NoConfigException("No configuration file found.", + $config_files); + } + + // Fixup for statusnet.ini + $_db_name = substr($config['db']['database'], strrpos($config['db']['database'], '/') + 1); + + if ($_db_name != 'statusnet' && !array_key_exists('ini_'.$_db_name, $config['db'])) { + $config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/statusnet.ini'; + } + + // Backwards compatibility + + if (array_key_exists('memcached', $config)) { + if ($config['memcached']['enabled']) { + addPlugin('Memcache', array('servers' => $config['memcached']['server'])); + } + + if (!empty($config['memcached']['base'])) { + $config['cache']['base'] = $config['memcached']['base']; + } + } + } +} + +class NoConfigException extends Exception +{ + public $config_files; + + function __construct($msg, $config_files) { + parent::__construct($msg); + $this->config_files = $config_files; + } +} diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index f059b42f00..3090e0bfb6 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -30,31 +30,150 @@ require_once 'Stomp.php'; -class LiberalStomp extends Stomp -{ - function getSocket() - { - return $this->_socket; - } -} -class StompQueueManager +class StompQueueManager extends QueueManager { var $server = null; var $username = null; var $password = null; var $base = null; var $con = null; + + protected $master = null; + protected $sites = array(); function __construct() { + parent::__construct(); $this->server = common_config('queue', 'stomp_server'); $this->username = common_config('queue', 'stomp_username'); $this->password = common_config('queue', 'stomp_password'); $this->base = common_config('queue', 'queue_basename'); } - function _connect() + /** + * Tell the i/o master we only need a single instance to cover + * all sites running in this process. + */ + public static function multiSite() + { + return IoManager::INSTANCE_PER_PROCESS; + } + + /** + * Record each site we'll be handling input for in this process, + * so we can listen to the necessary queues for it. + * + * @fixme possibly actually do subscription here to save another + * loop over all sites later? + */ + public function addSite($server) + { + $this->sites[] = $server; + } + + /** + * Saves a notice object reference into the queue item table. + * @return boolean true on success + */ + public function enqueue($object, $queue) + { + $notice = $object; + + $this->_connect(); + + // XXX: serialize and send entire notice + + $result = $this->con->send($this->queueName($queue), + $notice->id, // BODY of the message + array ('created' => $notice->created)); + + if (!$result) { + common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); + return false; + } + + common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' + . $notice->id . ' for ' . $queue); + $this->stats('enqueued', $queue); + } + + /** + * Send any sockets we're listening on to the IO manager + * to wait for input. + * + * @return array of resources + */ + public function getSockets() + { + return array($this->con->getSocket()); + } + + /** + * We've got input to handle on our socket! + * Read any waiting Stomp frame(s) and process them. + * + * @param resource $socket + * @return boolean ok on success + */ + public function handleInput($socket) + { + assert($socket === $this->con->getSocket()); + $ok = true; + $frames = $this->con->readFrames(); + foreach ($frames as $frame) { + $ok = $ok && $this->_handleNotice($frame); + } + return $ok; + } + + /** + * Initialize our connection and subscribe to all the queues + * we're going to need to handle... + * + * Side effects: in multi-site mode, may reset site configuration. + * + * @param IoMaster $master process/event controller + * @return bool return false on failure + */ + public function start($master) + { + parent::start($master); + if ($this->sites) { + foreach ($this->sites as $server) { + StatusNet::init($server); + $this->doSubscribe(); + } + } else { + $this->doSubscribe(); + } + return true; + } + + /** + * Subscribe to all the queues we're going to need to handle... + * + * Side effects: in multi-site mode, may reset site configuration. + * + * @return bool return false on failure + */ + public function finish() + { + if ($this->sites) { + foreach ($this->sites as $server) { + StatusNet::init($server); + $this->doUnsubscribe(); + } + } else { + $this->doUnsubscribe(); + } + return true; + } + + /** + * Lazy open connection to Stomp queue server. + */ + protected function _connect() { if (empty($this->con)) { $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'..."); @@ -69,97 +188,119 @@ class StompQueueManager } } - function enqueue($object, $queue) + /** + * Subscribe to all enabled notice queues for the current site. + */ + protected function doSubscribe() { - $notice = $object; - $this->_connect(); + foreach ($this->getQueues() as $queue) { + $rawqueue = $this->queueName($queue); + $this->_log(LOG_INFO, "Subscribing to $rawqueue"); + $this->con->subscribe($rawqueue); + } + } + + /** + * Subscribe from all enabled notice queues for the current site. + */ + protected function doUnsubscribe() + { + $this->_connect(); + foreach ($this->getQueues() as $queue) { + $this->con->unsubscribe($this->queueName($queue)); + } + } - // XXX: serialize and send entire notice + /** + * Handle and acknowledge a notice event that's come in through a queue. + * + * If the queue handler reports failure, the message is requeued for later. + * Missing notices or handler classes will drop the message. + * + * Side effects: in multi-site mode, may reset site configuration to + * match the site that queued the event. + * + * @param StompFrame $frame + * @return bool + */ + protected function _handleNotice($frame) + { + list($site, $queue) = $this->parseDestination($frame->headers['destination']); + if ($site != common_config('site', 'server')) { + $this->stats('switch'); + StatusNet::init($site); + } - $result = $this->con->send($this->_queueName($queue), - $notice->id, // BODY of the message - array ('created' => $notice->created)); + $id = intval($frame->body); + $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; - if (!$result) { - common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); + $notice = Notice::staticGet('id', $id); + if (empty($notice)) { + $this->_log(LOG_WARNING, "Skipping missing $info"); + $this->con->ack($frame); + $this->stats('badnotice', $queue); return false; } - common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' - . $notice->id . ' for ' . $queue); - } - - function service($queue, $handler) - { - $result = null; - - $this->_connect(); - - $this->con->setReadTimeout($handler->timeout()); - - $this->con->subscribe($this->_queueName($queue)); - - while (true) { - - // Wait for something on one of our sockets - - $stompsock = $this->con->getSocket(); - - $handsocks = $handler->getSockets(); - - $socks = array_merge(array($stompsock), $handsocks); - - $read = $socks; - $write = array(); - $except = array(); - - $ready = stream_select($read, $write, $except, $handler->timeout(), 0); - - if ($ready === false) { - $this->_log(LOG_ERR, "Error selecting on sockets"); - } else if ($ready > 0) { - if (in_array($stompsock, $read)) { - $this->_handleNotice($queue, $handler); - } - $handler->idle(QUEUE_HANDLER_HIT_IDLE); - } + $handler = $this->getHandler($queue); + if (!$handler) { + $this->_log(LOG_ERROR, "Missing handler class; skipping $info"); + $this->con->ack($frame); + $this->stats('badhandler', $queue); + return false; } - $this->con->unsubscribe($this->_queueName($queue)); - } + $ok = $handler->handle_notice($notice); - function _handleNotice($queue, $handler) - { - $frame = $this->con->readFrame(); - - if (!empty($frame)) { - $notice = Notice::staticGet('id', $frame->body); - - if (empty($notice)) { - $this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue); - $this->con->ack($frame); - } else { - if ($handler->handle_notice($notice)) { - $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue); - $this->con->ack($frame); - } else { - $this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue); - // FIXME we probably shouldn't have to do - // this kind of queue management ourselves - $this->con->ack($frame); - $this->enqueue($notice, $queue); - } - unset($notice); - } - - unset($frame); + if (!$ok) { + $this->_log(LOG_WARNING, "Failed handling $info"); + // FIXME we probably shouldn't have to do + // this kind of queue management ourselves; + // if we don't ack, it should resend... + $this->con->ack($frame); + $this->enqueue($notice, $queue); + $this->stats('requeued', $queue); + return false; } + + $this->_log(LOG_INFO, "Successfully handled $info"); + $this->con->ack($frame); + $this->stats('handled', $queue); + return true; } - function _queueName($queue) + /** + * Combines the queue_basename from configuration with the + * site server name and queue name to give eg: + * + * /queue/statusnet/identi.ca/sms + * + * @param string $queue + * @return string + */ + protected function queueName($queue) { - return common_config('queue', 'queue_basename') . $queue; + return common_config('queue', 'queue_basename') . + common_config('site', 'server') . '/' . $queue; + } + + /** + * Returns the site and queue name from the server-side queue. + * + * @param string queue destination (eg '/queue/statusnet/identi.ca/sms') + * @return array of site and queue: ('identi.ca','sms') or false if unrecognized + */ + protected function parseDestination($dest) + { + $prefix = common_config('queue', 'queue_basename'); + if (substr($dest, 0, strlen($prefix)) == $prefix) { + $rest = substr($dest, strlen($prefix)); + return explode("/", $rest, 2); + } else { + common_log(LOG_ERR, "Got a message from unrecognized stomp queue: $dest"); + return array(false, false); + } } function _log($level, $msg) @@ -167,3 +308,4 @@ class StompQueueManager common_log($level, 'StompQueueManager: '.$msg); } } + diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index 72dbc4eede..5595eac052 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -23,57 +23,35 @@ * @package StatusNet * @author Evan Prodromou * @author Sarven Capadisli + * @author Brion Vibber * @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/ */ -class UnQueueManager +class UnQueueManager extends QueueManager { + + /** + * Dummy queue storage manager: instead of saving events for later, + * we just process them immediately. This is only suitable for events + * that can be processed quickly and don't need polling or long-running + * connections to another server such as XMPP. + * + * @param Notice $object + * @param string $queue + */ function enqueue($object, $queue) { $notice = $object; - - switch ($queue) - { - case 'omb': - if ($this->_isLocal($notice)) { - require_once(INSTALLDIR.'/lib/omb.php'); - omb_broadcast_notice($notice); - } - break; - case 'public': - if ($this->_isLocal($notice)) { - require_once(INSTALLDIR.'/lib/jabber.php'); - jabber_public_notice($notice); - } - break; - case 'ping': - if ($this->_isLocal($notice)) { - require_once INSTALLDIR . '/lib/ping.php'; - return ping_broadcast_notice($notice); - } - case 'sms': - require_once(INSTALLDIR.'/lib/mail.php'); - mail_broadcast_notice_sms($notice); - break; - case 'jabber': - require_once(INSTALLDIR.'/lib/jabber.php'); - jabber_broadcast_notice($notice); - break; - case 'plugin': - Event::handle('HandleQueuedNotice', array(&$notice)); - break; - default: + + $handler = $this->getHandler($queue); + if ($handler) { + $handler->handle_notice($notice); + } else { if (Event::handle('UnqueueHandleNotice', array(&$notice, $queue))) { throw new ServerException("UnQueueManager: Unknown queue: $queue"); } } } - - function _isLocal($notice) - { - return ($notice->is_local == Notice::LOCAL_PUBLIC || - $notice->is_local == Notice::LOCAL_NONPUBLIC); - } -} \ No newline at end of file +} diff --git a/lib/util.php b/lib/util.php index 1237d718bd..f161794e38 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1132,8 +1132,9 @@ function common_log_line($priority, $msg) function common_request_id() { $pid = getmypid(); + $server = common_config('site', 'server'); if (php_sapi_name() == 'cli') { - return $pid; + return "$server:$pid"; } else { static $req_id = null; if (!isset($req_id)) { @@ -1143,7 +1144,7 @@ function common_request_id() $url = $_SERVER['REQUEST_URI']; } $method = $_SERVER['REQUEST_METHOD']; - return "$pid.$req_id $method $url"; + return "$server:$pid.$req_id $method $url"; } } diff --git a/lib/xmppconfirmmanager.php b/lib/xmppconfirmmanager.php new file mode 100644 index 0000000000..ee4e294fd4 --- /dev/null +++ b/lib/xmppconfirmmanager.php @@ -0,0 +1,168 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Event handler for pushing new confirmations to Jabber users. + * @fixme recommend redoing this on a queue-trigger model + * @fixme expiration of old items got dropped in the past, put it back? + */ +class XmppConfirmManager extends IoManager +{ + + /** + * @return mixed XmppConfirmManager, or false if unneeded + */ + public static function get() + { + if (common_config('xmpp', 'enabled')) { + $site = common_config('site', 'server'); + return new XmppConfirmManager(); + } else { + return false; + } + } + + /** + * Tell the i/o master we need one instance for each supporting site + * being handled in this process. + */ + public static function multiSite() + { + return IoManager::INSTANCE_PER_SITE; + } + + function __construct() + { + $this->site = common_config('site', 'server'); + } + + /** + * 10 seconds? Really? That seems a bit frequent. + */ + function pollInterval() + { + return 10; + } + + /** + * Ping! + * @return boolean true if we found something + */ + function poll() + { + $this->switchSite(); + $confirm = $this->next_confirm(); + if ($confirm) { + $this->handle_confirm($confirm); + return true; + } else { + return false; + } + } + + protected function handle_confirm($confirm) + { + require_once INSTALLDIR . '/lib/jabber.php'; + + common_log(LOG_INFO, 'Sending confirmation for ' . $confirm->address); + $user = User::staticGet($confirm->user_id); + if (!$user) { + common_log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id); + return; + } + $success = jabber_confirm_address($confirm->code, + $user->nickname, + $confirm->address); + if (!$success) { + common_log(LOG_ERR, 'Confirmation failed for ' . $confirm->address); + # Just let the claim age out; hopefully things work then + return; + } else { + common_log(LOG_INFO, 'Confirmation sent for ' . $confirm->address); + # Mark confirmation sent; need a dupe so we don't have the WHERE clause + $dupe = Confirm_address::staticGet('code', $confirm->code); + if (!$dupe) { + common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__); + return; + } + $orig = clone($dupe); + $dupe->sent = $dupe->claimed; + $result = $dupe->update($orig); + if (!$result) { + common_log_db_error($dupe, 'UPDATE', __FILE__); + # Just let the claim age out; hopefully things work then + return; + } + } + return true; + } + + protected function next_confirm() + { + $confirm = new Confirm_address(); + $confirm->whereAdd('claimed IS null'); + $confirm->whereAdd('sent IS null'); + # XXX: eventually we could do other confirmations in the queue, too + $confirm->address_type = 'jabber'; + $confirm->orderBy('modified DESC'); + $confirm->limit(1); + if ($confirm->find(true)) { + common_log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address); + # working around some weird DB_DataObject behaviour + $confirm->whereAdd(''); # clears where stuff + $original = clone($confirm); + $confirm->claimed = common_sql_now(); + $result = $confirm->update($original); + if ($result) { + common_log(LOG_INFO, 'Succeeded in claim! '. $result); + return $confirm; + } else { + common_log(LOG_INFO, 'Failed in claim!'); + return false; + } + } + return null; + } + + protected function clear_old_confirm_claims() + { + $confirm = new Confirm(); + $confirm->claimed = null; + $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT); + $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY); + $confirm->free(); + unset($confirm); + } + + /** + * Make sure we're on the right site configuration + */ + protected function switchSite() + { + if ($this->site != common_config('site', 'server')) { + common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); + $this->stats('switch'); + StatusNet::init($this->site); + } + } +} diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php new file mode 100644 index 0000000000..9662e97d15 --- /dev/null +++ b/lib/xmppmanager.php @@ -0,0 +1,273 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * XMPP background connection manager for XMPP-using queue handlers, + * allowing them to send outgoing messages on the right connection. + * + * Input is handled during socket select loop, keepalive pings during idle. + * Any incoming messages will be forwarded to the main XmppDaemon process, + * which handles direct user interaction. + * + * In a multi-site queuedaemon.php run, one connection will be instantiated + * for each site being handled by the current process that has XMPP enabled. + */ + +class XmppManager extends IoManager +{ + protected $site = null; + protected $pingid = 0; + protected $lastping = null; + + static protected $singletons = array(); + + const PING_INTERVAL = 120; + + /** + * Fetch the singleton XmppManager for the current site. + * @return mixed XmppManager, or false if unneeded + */ + public static function get() + { + if (common_config('xmpp', 'enabled')) { + $site = common_config('site', 'server'); + if (empty(self::$singletons[$site])) { + self::$singletons[$site] = new XmppManager(); + } + return self::$singletons[$site]; + } else { + return false; + } + } + + /** + * Tell the i/o master we need one instance for each supporting site + * being handled in this process. + */ + public static function multiSite() + { + return IoManager::INSTANCE_PER_SITE; + } + + function __construct() + { + $this->site = common_config('site', 'server'); + } + + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + parent::start($master); + $this->switchSite(); + + require_once "lib/jabber.php"; + + # Low priority; we don't want to receive messages + + common_log(LOG_INFO, "INITIALIZE"); + $this->conn = jabber_connect($this->resource()); + + if (empty($this->conn)) { + common_log(LOG_ERR, "Couldn't connect to server."); + return false; + } + + $this->conn->addEventHandler('message', 'forward_message', $this); + $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); + $this->conn->setReconnectTimeout(600); + jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); + + return !is_null($this->conn); + } + + /** + * Message pump is triggered on socket input, so we only need an idle() + * call often enough to trigger our outgoing pings. + */ + function timeout() + { + return self::PING_INTERVAL; + } + + /** + * Lists the XMPP connection socket to allow i/o master to wake + * when input comes in here as well as from the queue source. + * + * @return array of resources + */ + public function getSockets() + { + return array($this->conn->getSocket()); + } + + /** + * Process XMPP events that have come in over the wire. + * Side effects: may switch site configuration + * @fixme may kill process on XMPP error + * @param resource $socket + */ + public function handleInput($socket) + { + $this->switchSite(); + + # Process the queue for as long as needed + try { + if ($this->conn) { + assert($socket === $this->conn->getSocket()); + + common_log(LOG_DEBUG, "Servicing the XMPP queue."); + $this->stats('xmpp_process'); + $this->conn->processTime(0); + } + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); + die($e->getMessage()); + } + } + + /** + * Idle processing for io manager's execution loop. + * Send keepalive pings to server. + * + * Side effect: kills process on exception from XMPP library. + * + * @fixme non-dying error handling + */ + public function idle($timeout=0) + { + if ($this->conn) { + $now = time(); + if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { + $this->switchSite(); + try { + $this->sendPing(); + $this->lastping = $now; + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); + die($e->getMessage()); + } + } + } + } + + /** + * Send a keepalive ping to the XMPP server. + */ + protected function sendPing() + { + $jid = jabber_daemon_address().'/'.$this->resource(); + $server = common_config('xmpp', 'server'); + + if (!isset($this->pingid)) { + $this->pingid = 0; + } else { + $this->pingid++; + } + + common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); + + $this->conn->send(""); + } + + /** + * Callback for Jabber reconnect event + * @param $pl + */ + function handle_reconnect(&$pl) + { + common_log(LOG_NOTICE, 'XMPP reconnected'); + + $this->conn->processUntil('session_start'); + $this->conn->presence(null, 'available', null, 'available', -1); + } + + /** + * Callback for Jabber message event. + * + * This connection handles output; if we get a message straight to us, + * forward it on to our XmppDaemon listener for processing. + * + * @param $pl + */ + function forward_message(&$pl) + { + if ($pl['type'] != 'chat') { + common_log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']); + return; + } + $listener = $this->listener(); + if (strtolower($listener) == strtolower($pl['from'])) { + common_log(LOG_WARNING, 'Ignoring loop message.'); + return; + } + common_log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener); + $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from'])); + } + + /** + * Build an block with an ofrom entry for forwarded messages + * + * @param string $from Jabber ID of original sender + * @return string XML fragment + */ + protected function ofrom($from) + { + $address = "\n"; + $address .= "
\n"; + $address .= "\n"; + return $address; + } + + /** + * Build the complete JID of the XmppDaemon process which + * handles primary XMPP input for this site. + * + * @return string Jabber ID + */ + protected function listener() + { + if (common_config('xmpp', 'listener')) { + return common_config('xmpp', 'listener'); + } else { + return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon'; + } + } + + protected function resource() + { + return 'queue' . posix_getpid(); // @fixme PIDs won't be host-unique + } + + /** + * Make sure we're on the right site configuration + */ + protected function switchSite() + { + if ($this->site != common_config('site', 'server')) { + common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); + $this->stats('switch'); + StatusNet::init($this->site); + } + } +} diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php deleted file mode 100644 index f28fc9088c..0000000000 --- a/lib/xmppqueuehandler.php +++ /dev/null @@ -1,142 +0,0 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/queuehandler.php'); - -define('PING_INTERVAL', 120); - -/** - * Common superclass for all XMPP-using queue handlers. They all need to - * service their message queues on idle, and forward any incoming messages - * to the XMPP listener connection. So, we abstract out common code to a - * superclass. - */ - -class XmppQueueHandler extends QueueHandler -{ - var $pingid = 0; - var $lastping = null; - - function start() - { - # Low priority; we don't want to receive messages - - $this->log(LOG_INFO, "INITIALIZE"); - $this->conn = jabber_connect($this->_id.$this->transport()); - - if (empty($this->conn)) { - $this->log(LOG_ERR, "Couldn't connect to server."); - return false; - } - - $this->conn->addEventHandler('message', 'forward_message', $this); - $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); - $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', -1); - - return !is_null($this->conn); - } - - function timeout() - { - return 10; - } - - function handle_reconnect(&$pl) - { - $this->log(LOG_NOTICE, 'reconnected'); - - $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', -1); - } - - function idle($timeout=0) - { - # Process the queue for as long as needed - try { - if ($this->conn) { - $this->log(LOG_DEBUG, "Servicing the XMPP queue."); - $this->conn->processTime($timeout); - $now = time(); - if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) { - $this->sendPing(); - $this->lastping = $now; - } - } - } catch (XMPPHP_Exception $e) { - $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } - } - - function sendPing() - { - $jid = jabber_daemon_address().'/'.$this->_id.$this->transport(); - $server = common_config('xmpp', 'server'); - - if (!isset($this->pingid)) { - $this->pingid = 0; - } else { - $this->pingid++; - } - - $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}"); - - $this->conn->send(""); - } - - function forward_message(&$pl) - { - if ($pl['type'] != 'chat') { - $this->log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']); - return; - } - $listener = $this->listener(); - if (strtolower($listener) == strtolower($pl['from'])) { - $this->log(LOG_WARNING, 'Ignoring loop message.'); - return; - } - $this->log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener); - $this->conn->message($this->listener(), $pl['body'], 'chat', null, $this->ofrom($pl['from'])); - } - - function ofrom($from) - { - $address = "\n"; - $address .= "
\n"; - $address .= "\n"; - return $address; - } - - function listener() - { - if (common_config('xmpp', 'listener')) { - return common_config('xmpp', 'listener'); - } else { - return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon'; - } - } - - function getSockets() - { - return array($this->conn->getSocket()); - } -} diff --git a/plugins/Enjit/README b/plugins/Enjit/README new file mode 100644 index 0000000000..03f989490b --- /dev/null +++ b/plugins/Enjit/README @@ -0,0 +1,5 @@ +This doesn't seem to have been functional for a while; can't find other references +to the enjit configuration or transport enqueuing. Keeping it in case someone +wants to bring it up to date. + +-- brion vibber 2009-12-03 diff --git a/scripts/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php old mode 100755 new mode 100644 similarity index 79% rename from scripts/enjitqueuehandler.php rename to plugins/Enjit/enjitqueuehandler.php index afcac539a6..f0e706b929 --- a/scripts/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = << 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new EnjitQueueHandler($id); - -if ($handler->start()) { - $handler->handle_queue(); -} - -$handler->finish(); diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index de91bf24a1..4266b886d9 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -114,6 +114,9 @@ class FacebookPlugin extends Plugin case 'FBCSettingsNav': include_once INSTALLDIR . '/plugins/Facebook/FBCSettingsNav.php'; return false; + case 'FacebookQueueHandler': + include_once INSTALLDIR . '/plugins/Facebook/facebookqueuehandler.php'; + return false; default: return true; } @@ -508,50 +511,15 @@ class FacebookPlugin extends Plugin } /** - * broadcast the message when not using queuehandler + * Register Facebook notice queue handler * - * @param Notice &$notice the notice - * @param array $queue destination queue + * @param QueueManager $manager * * @return boolean hook return */ - - function onUnqueueHandleNotice(&$notice, $queue) + function onEndInitializeQueueManager($manager) { - if (($queue == 'facebook') && ($this->_isLocal($notice))) { - facebookBroadcastNotice($notice); - return false; - } - return true; - } - - /** - * Determine whether the notice was locally created - * - * @param Notice $notice the notice - * - * @return boolean locality - */ - - function _isLocal($notice) - { - return ($notice->is_local == Notice::LOCAL_PUBLIC || - $notice->is_local == Notice::LOCAL_NONPUBLIC); - } - - /** - * Add Facebook queuehandler to the list of daemons to start - * - * @param array $daemons the list fo daemons to run - * - * @return boolean hook return - * - */ - - function onGetValidDaemons($daemons) - { - array_push($daemons, INSTALLDIR . - '/plugins/Facebook/facebookqueuehandler.php'); + $manager->connect('facebook', 'FacebookQueueHandler'); return true; } diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php old mode 100755 new mode 100644 index e4ae7d4ee7..1778690e5b --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); + if ($this->_isLocal($notice)) { + return facebookBroadcastNotice($notice); + } return true; } - function handle_notice($notice) + /** + * Determine whether the notice was locally created + * + * @param Notice $notice the notice + * + * @return boolean locality + */ + function _isLocal($notice) { - return facebookBroadcastNotice($notice); + return ($notice->is_local == Notice::LOCAL_PUBLIC || + $notice->is_local == Notice::LOCAL_NONPUBLIC); } - - function finish() - { - } - } - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new FacebookQueueHandler($id); - -$handler->runOnce(); diff --git a/plugins/MemcachePlugin.php b/plugins/MemcachePlugin.php index 5f93e9a836..fbc2802f78 100644 --- a/plugins/MemcachePlugin.php +++ b/plugins/MemcachePlugin.php @@ -133,6 +133,23 @@ class MemcachePlugin extends Plugin return false; } + function onStartCacheReconnect(&$success) + { + if (empty($this->_conn)) { + // nothing to do + return true; + } + if ($this->persistent) { + common_log(LOG_ERR, "Cannot close persistent memcached connection"); + $success = false; + } else { + common_log(LOG_INFO, "Closing memcached connection"); + $success = $this->_conn->close(); + $this->_conn = null; + } + return false; + } + /** * Ensure that a connection exists * diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index a87ee2894a..57b3c1c995 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -112,7 +112,9 @@ class TwitterBridgePlugin extends Plugin strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'TwitterOAuthClient': - include_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php'; + case 'TwitterQueueHandler': + include_once INSTALLDIR . '/plugins/TwitterBridge/' . + strtolower($cls) . '.php'; return false; default: return true; @@ -138,48 +140,15 @@ class TwitterBridgePlugin extends Plugin return true; } - /** - * broadcast the message when not using queuehandler - * - * @param Notice &$notice the notice - * @param array $queue destination queue - * - * @return boolean hook return - */ - function onUnqueueHandleNotice(&$notice, $queue) - { - if (($queue == 'twitter') && ($this->_isLocal($notice))) { - broadcast_twitter($notice); - return false; - } - return true; - } - - /** - * Determine whether the notice was locally created - * - * @param Notice $notice - * - * @return boolean locality - */ - function _isLocal($notice) - { - return ($notice->is_local == Notice::LOCAL_PUBLIC || - $notice->is_local == Notice::LOCAL_NONPUBLIC); - } - /** * Add Twitter bridge daemons to the list of daemons to start * * @param array $daemons the list fo daemons to run * * @return boolean hook return - * */ function onGetValidDaemons($daemons) { - array_push($daemons, INSTALLDIR . - '/plugins/TwitterBridge/daemons/twitterqueuehandler.php'); array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); @@ -191,6 +160,19 @@ class TwitterBridgePlugin extends Plugin return true; } + /** + * Register Twitter notice queue handler + * + * @param QueueManager $manager + * + * @return boolean hook return + */ + function onEndInitializeQueueManager($manager) + { + $manager->connect('twitter', 'TwitterQueueHandler'); + return true; + } + function onPluginVersion(&$versions) { $versions[] = array('name' => 'TwitterBridge', diff --git a/plugins/TwitterBridge/daemons/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php old mode 100755 new mode 100644 similarity index 57% rename from plugins/TwitterBridge/daemons/twitterqueuehandler.php rename to plugins/TwitterBridge/twitterqueuehandler.php index f0e76bb745..5089ca7b74 --- a/plugins/TwitterBridge/daemons/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - function handle_notice($notice) { return broadcast_twitter($notice); } - - function finish() - { - } - } - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new TwitterQueueHandler($id); - -$handler->runOnce(); diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 99ad41b374..a332e06b58 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -37,19 +37,10 @@ require_once INSTALLDIR.'/scripts/commandline.inc'; $daemons = array(); -$daemons[] = INSTALLDIR.'/scripts/pluginqueuehandler.php'; -$daemons[] = INSTALLDIR.'/scripts/ombqueuehandler.php'; -$daemons[] = INSTALLDIR.'/scripts/pingqueuehandler.php'; +$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php'; if(common_config('xmpp','enabled')) { $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php'; - $daemons[] = INSTALLDIR.'/scripts/jabberqueuehandler.php'; - $daemons[] = INSTALLDIR.'/scripts/publicqueuehandler.php'; - $daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php'; -} - -if (common_config('sms', 'enabled')) { - $daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php'; } if (Event::handle('GetValidDaemons', array(&$daemons))) { diff --git a/scripts/pingqueuehandler.php b/scripts/handlequeued.php similarity index 52% rename from scripts/pingqueuehandler.php rename to scripts/handlequeued.php index c92337e36c..9031437aac 100755 --- a/scripts/pingqueuehandler.php +++ b/scripts/handlequeued.php @@ -20,50 +20,37 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); -$shortoptions = 'i::'; -$longoptions = array('id::'); +$helptext = << +Run a single queued notice through background processing +as if it were being run through the queue. -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - - function handle_notice($notice) { - return ping_broadcast_notice($notice); - } - - function finish() { - } +if (count($args) != 2) { + show_help(); } -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; +$queue = trim($args[0]); +$noticeId = intval($args[1]); + +$qm = QueueManager::get(); +$handler = $qm->getHandler($queue); +if (!$handler) { + print "No handler for queue '$queue'.\n"; + exit(1); } -$handler = new PingQueueHandler($id); +$notice = Notice::staticGet('id', $noticeId); +if (empty($notice)) { + print "Invalid notice id $noticeId\n"; + exit(1); +} -$handler->runOnce(); +if (!$handler->handle_notice($notice)) { + print "Failed to handle notice id $noticeId on queue '$queue'.\n"; + exit(1); +} diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php new file mode 100755 index 0000000000..8ef364fe7b --- /dev/null +++ b/scripts/queuedaemon.php @@ -0,0 +1,265 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'fi:at:'; +$longoptions = array('id=', 'foreground', 'all', 'threads='); + +/** + * Attempts to get a count of the processors available on the current system + * to fan out multiple threads. + * + * Recognizes Linux and Mac OS X; others will return default of 1. + * + * @return intval + */ +function getProcessorCount() +{ + $cpus = 0; + switch (PHP_OS) { + case 'Linux': + $cpuinfo = file('/proc/cpuinfo'); + foreach (file('/proc/cpuinfo') as $line) { + if (preg_match('/^processor\s+:\s+(\d+)\s?$/', $line)) { + $cpus++; + } + } + break; + case 'Darwin': + $cpus = intval(shell_exec("/usr/sbin/sysctl -n hw.ncpu 2>/dev/null")); + break; + } + if ($cpus) { + return $cpus; + } + return 1; +} + +$threads = getProcessorCount(); +$helptext = << Spawn processing threads (default $threads) + + +END_OF_QUEUE_HELP; + +require_once INSTALLDIR.'/scripts/commandline.inc'; + +require_once(INSTALLDIR.'/lib/daemon.php'); +require_once(INSTALLDIR.'/classes/Queue_item.php'); +require_once(INSTALLDIR.'/classes/Notice.php'); + +define('CLAIM_TIMEOUT', 1200); + +/** + * Queue handling daemon... + * + * The queue daemon by default launches in the background, at which point + * it'll pass control to the configured QueueManager class to poll for updates. + * + * We can then pass individual items through the QueueHandler subclasses + * they belong to. + */ +class QueueDaemon extends Daemon +{ + protected $allsites; + protected $threads=1; + + function __construct($id=null, $daemonize=true, $threads=1, $allsites=false) + { + parent::__construct($daemonize); + + if ($id) { + $this->set_id($id); + } + $this->all = $allsites; + $this->threads = $threads; + } + + /** + * How many seconds a polling-based queue manager should wait between + * checks for new items to handle. + * + * Defaults to 60 seconds; override to speed up or slow down. + * + * @return int timeout in seconds + */ + function timeout() + { + return 60; + } + + function name() + { + return strtolower(get_class($this).'.'.$this->get_id()); + } + + function run() + { + if ($this->threads > 1) { + return $this->runThreads(); + } else { + return $this->runLoop(); + } + } + + function runThreads() + { + $children = array(); + for ($i = 1; $i <= $this->threads; $i++) { + $pid = pcntl_fork(); + if ($pid < 0) { + print "Couldn't fork for thread $i; aborting\n"; + exit(1); + } else if ($pid == 0) { + $this->runChild($i); + exit(0); + } else { + $this->log(LOG_INFO, "Spawned thread $i as pid $pid"); + $children[$i] = $pid; + } + } + + $this->log(LOG_INFO, "Waiting for children to complete."); + while (count($children) > 0) { + $status = null; + $pid = pcntl_wait($status); + if ($pid > 0) { + $i = array_search($pid, $children); + if ($i === false) { + $this->log(LOG_ERR, "Unrecognized child pid $pid exited!"); + continue; + } + unset($children[$i]); + $this->log(LOG_INFO, "Thread $i pid $pid exited."); + + $pid = pcntl_fork(); + if ($pid < 0) { + print "Couldn't fork to respawn thread $i; aborting thread.\n"; + } else if ($pid == 0) { + $this->runChild($i); + exit(0); + } else { + $this->log(LOG_INFO, "Respawned thread $i as pid $pid"); + $children[$i] = $pid; + } + } + } + $this->log(LOG_INFO, "All child processes complete."); + return true; + } + + function runChild($thread) + { + $this->set_id($this->get_id() . "." . $thread); + $this->resetDb(); + $this->runLoop(); + } + + /** + * Reconnect to the database for each child process, + * or they'll get very confused trying to use the + * same socket. + */ + function resetDb() + { + // @fixme do we need to explicitly open the db too + // or is this implied? + global $_DB_DATAOBJECT; + unset($_DB_DATAOBJECT['CONNECTIONS']); + + // Reconnect main memcached, or threads will stomp on + // each other and corrupt their requests. + $cache = common_memcache(); + if ($cache) { + $cache->reconnect(); + } + + // Also reconnect memcached for status_network table. + if (!empty(Status_network::$cache)) { + Status_network::$cache->close(); + Status_network::$cache = null; + } + } + + /** + * Setup and start of run loop for this queue handler as a daemon. + * Most of the heavy lifting is passed on to the QueueManager's service() + * method, which passes control on to the QueueHandler's handle_notice() + * method for each notice that comes in on the queue. + * + * Most of the time this won't need to be overridden in a subclass. + * + * @return boolean true on success, false on failure + */ + function runLoop() + { + $this->log(LOG_INFO, 'checking for queued notices'); + + $master = new IoMaster($this->get_id()); + $master->init($this->all); + $master->service(); + + $this->log(LOG_INFO, 'finished servicing the queue'); + + $this->log(LOG_INFO, 'terminating normally'); + + return true; + } + + function log($level, $msg) + { + common_log($level, get_class($this) . ' ('. $this->get_id() .'): '.$msg); + } +} + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +if (have_option('t')) { + $threads = intval(get_option_value('t')); +} else if (have_option('--threads')) { + $threads = intval(get_option_value('--threads')); +} else { + $threads = 0; +} +if (!$threads) { + $threads = getProcessorCount(); +} + +$daemonize = !(have_option('f') || have_option('--foreground')); +$all = have_option('a') || have_option('--all'); + +$daemon = new QueueDaemon($id, $daemonize, $threads, $all); +$daemon->runOnce(); + diff --git a/scripts/xmppconfirmhandler.php b/scripts/xmppconfirmhandler.php deleted file mode 100755 index 2e39741369..0000000000 --- a/scripts/xmppconfirmhandler.php +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<start()) { - return false; - } - $this->log(LOG_INFO, 'checking for queued confirmations'); - do { - $confirm = $this->next_confirm(); - if ($confirm) { - $this->log(LOG_INFO, 'Sending confirmation for ' . $confirm->address); - $user = User::staticGet($confirm->user_id); - if (!$user) { - $this->log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id); - continue; - } - $success = jabber_confirm_address($confirm->code, - $user->nickname, - $confirm->address); - if (!$success) { - $this->log(LOG_ERR, 'Confirmation failed for ' . $confirm->address); - # Just let the claim age out; hopefully things work then - continue; - } else { - $this->log(LOG_INFO, 'Confirmation sent for ' . $confirm->address); - # Mark confirmation sent; need a dupe so we don't have the WHERE clause - $dupe = Confirm_address::staticGet('code', $confirm->code); - if (!$dupe) { - common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__); - continue; - } - $orig = clone($dupe); - $dupe->sent = $dupe->claimed; - $result = $dupe->update($orig); - if (!$result) { - common_log_db_error($dupe, 'UPDATE', __FILE__); - # Just let the claim age out; hopefully things work then - continue; - } - $dupe->free(); - unset($dupe); - } - $user->free(); - unset($user); - $confirm->free(); - unset($confirm); - $this->idle(0); - } else { -# $this->clear_old_confirm_claims(); - $this->idle(10); - } - } while (true); - if (!$this->finish()) { - return false; - } - return true; - } - - function next_confirm() - { - $confirm = new Confirm_address(); - $confirm->whereAdd('claimed IS null'); - $confirm->whereAdd('sent IS null'); - # XXX: eventually we could do other confirmations in the queue, too - $confirm->address_type = 'jabber'; - $confirm->orderBy('modified DESC'); - $confirm->limit(1); - if ($confirm->find(true)) { - $this->log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address); - # working around some weird DB_DataObject behaviour - $confirm->whereAdd(''); # clears where stuff - $original = clone($confirm); - $confirm->claimed = common_sql_now(); - $result = $confirm->update($original); - if ($result) { - $this->log(LOG_INFO, 'Succeeded in claim! '. $result); - return $confirm; - } else { - $this->log(LOG_INFO, 'Failed in claim!'); - return false; - } - } - return null; - } - - function clear_old_confirm_claims() - { - $confirm = new Confirm(); - $confirm->claimed = null; - $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT); - $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY); - $confirm->free(); - unset($confirm); - } -} - -// Abort immediately if xmpp is not enabled, otherwise the daemon chews up -// lots of CPU trying to connect to unconfigured servers -if (common_config('xmpp','enabled')==false) { - print "Aborting daemon - xmpp is disabled\n"; - exit(); -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new XmppConfirmHandler($id); - -$handler->runOnce(); - From fb0d837ddc572993f8c6d1db3de2c96d70101841 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 12 Jan 2010 23:41:33 -0800 Subject: [PATCH 35/57] remove transaction for Notice save; causes deadlocks --- classes/Notice.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 6284b8ca51..3069564227 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -299,8 +299,6 @@ class Notice extends Memcached_DataObject // XXX: some of these functions write to the DB - $notice->query('BEGIN'); - $id = $notice->insert(); if (!$id) { @@ -342,8 +340,6 @@ class Notice extends Memcached_DataObject $notice->saveUrls(); - $notice->query('COMMIT'); - Event::handle('EndNoticeSave', array($notice)); } From cc25d3baf0d503f469f9621c53513370453ad8ee Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 12 Jan 2010 23:56:21 -0800 Subject: [PATCH 36/57] ws changes for phpcs --- lib/statusnet.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/statusnet.php b/lib/statusnet.php index 0c5807d7b9..29e9030267 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -1,7 +1,7 @@ . + * */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} global $config, $_server, $_path; @@ -227,19 +230,19 @@ class StatusNet protected function loadConfigFile($conffile=null) { global $_server, $_path, $config; - + // From most general to most specific: // server-wide, then vhost-wide, then for a path, // finally for a dir (usually only need one of the last two). - + if (isset($conffile)) { $config_files = array($conffile); } else { $config_files = array('/etc/statusnet/statusnet.php', - '/etc/statusnet/laconica.php', - '/etc/laconica/laconica.php', - '/etc/statusnet/'.$_server.'.php', - '/etc/laconica/'.$_server.'.php'); + '/etc/statusnet/laconica.php', + '/etc/laconica/laconica.php', + '/etc/statusnet/'.$_server.'.php', + '/etc/laconica/'.$_server.'.php'); if (strlen($_path) > 0) { $config_files[] = '/etc/statusnet/'.$_server.'_'.$_path.'.php'; @@ -260,7 +263,7 @@ class StatusNet if (!self::$have_config) { throw new NoConfigException("No configuration file found.", - $config_files); + $config_files); } // Fixup for statusnet.ini From 155e4ef4f32413452acf1b0bc6486d85bfc71cc0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 12 Jan 2010 23:58:25 -0800 Subject: [PATCH 37/57] remove some debugging statements from util.php --- lib/util.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/util.php b/lib/util.php index df6bee4d46..9255b9b376 100644 --- a/lib/util.php +++ b/lib/util.php @@ -191,7 +191,6 @@ function common_ensure_session() } } } - common_debug("Session ID = " . session_id()); } // Three kinds of arguments: @@ -258,7 +257,6 @@ function common_rememberme($user=null) if (!$user) { $user = common_current_user(); if (!$user) { - common_debug('No current user to remember', __FILE__); return false; } } @@ -276,14 +274,11 @@ function common_rememberme($user=null) if (!$result) { common_log_db_error($rm, 'INSERT', __FILE__); - common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__); return false; } $rm->query('COMMIT'); - common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__); - $cookieval = $rm->user_id . ':' . $rm->code; common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname); @@ -391,8 +386,6 @@ function common_current_user() $_cur = common_remembered_user(); if ($_cur) { - common_debug("Got User " . $_cur->nickname); - common_debug("Faking session on remembered user"); // XXX: Is this necessary? $_SESSION['userid'] = $_cur->id; } From 3d579fc58007ccb20657bc3959aad220e72e8f30 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 00:16:54 -0800 Subject: [PATCH 38/57] memoize Notice::whoGets() --- classes/Notice.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 3069564227..c8edc98e1a 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -828,6 +828,15 @@ class Notice extends Memcached_DataObject function whoGets() { + $c = self::memcache(); + + if (!empty($c)) { + $ni = $c->get(common_cache_key('notice:who_gets:'.$this->id)); + if ($ni !== false) { + return $ni; + } + } + $users = $this->getSubscribedUsers(); // FIXME: kind of ignoring 'transitional'... @@ -867,6 +876,11 @@ class Notice extends Memcached_DataObject } } + if (!empty($c)) { + // XXX: pack this data better + $c->set(common_cache_key('notice:who_gets:'.$this->id), $ni); + } + return $ni; } From b25e59a11fb5eb6ea97d8b9c6081d1e1585febb3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 00:25:43 -0800 Subject: [PATCH 39/57] add a script for initializing inboxes --- scripts/initializeinbox.php | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 scripts/initializeinbox.php diff --git a/scripts/initializeinbox.php b/scripts/initializeinbox.php new file mode 100644 index 0000000000..43afc48eb2 --- /dev/null +++ b/scripts/initializeinbox.php @@ -0,0 +1,94 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i:n:af'; +$longoptions = array('id=', 'nickname=', 'all', 'force'); + +$helptext = <<find()) { + while ($user->fetch()) { + initializeInbox($user); + } + } + } else { + show_help(); + exit(1); + } +} catch (Exception $e) { + print $e->getMessage()."\n"; + exit(1); +} + +function initializeInbox($user) +{ + if (!have_option('q', 'quiet')) { + print "Initializing inbox for $user->nickname..."; + } + + $inbox = Inbox::staticGet('user_id', $user_id); + + if (!empty($inbox)) { + if (!have_option('q', 'quiet')) { + print "SKIP\n"; + } + } else { + $inbox = Inbox::initialize($user_id); + if (!have_option('q', 'quiet')) { + if (empty($inbox)) { + print "ERR\n"; + } else { + print "DONE\n"; + } + } + } +} From 1bbd19169f22b9405b4ee1964da7e94a228a8dd5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 00:44:29 -0800 Subject: [PATCH 40/57] unimplement repeatedToMe() until we figure out how --- classes/User.php | 51 +----------------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/classes/User.php b/classes/User.php index bde3f71b92..d6b52be017 100644 --- a/classes/User.php +++ b/classes/User.php @@ -899,56 +899,7 @@ class User extends Memcached_DataObject function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) { - $ids = Notice::stream(array($this, '_repeatedToMeDirect'), - array(), - 'user:repeated_to_me:'.$this->id, - $offset, $limit, $since_id, $max_id, null); - - return Notice::getStreamByIds($ids); - } - - function _repeatedToMeDirect($offset, $limit, $since_id, $max_id, $since) - { - $qry = - 'SELECT notice.id AS id ' . - 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' . - 'WHERE notice_inbox.user_id = ' . $this->id . ' ' . - 'AND notice.repeat_of IS NOT NULL '; - - if ($since_id != 0) { - $qry .= 'AND notice.id > ' . $since_id . ' '; - } - - if ($max_id != 0) { - $qry .= 'AND notice.id <= ' . $max_id . ' '; - } - - if (!is_null($since)) { - $qry .= 'AND notice.modified > \'' . date('Y-m-d H:i:s', $since) . '\' '; - } - - // NOTE: we sort by fave time, not by notice time! - - $qry .= 'ORDER BY notice.id DESC '; - - if (!is_null($offset)) { - $qry .= "LIMIT $limit OFFSET $offset"; - } - - $ids = array(); - - $notice = new Notice(); - - $notice->query($qry); - - while ($notice->fetch()) { - $ids[] = $notice->id; - } - - $notice->free(); - $notice = NULL; - - return $ids; + throw new Exception("Not implemented since inbox change."); } function shareLocation() From 5397249e505b6e82c814c2a7ce5bc1bfaad2adf3 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 00:45:12 -0800 Subject: [PATCH 41/57] remove vestiges of Notice_inbox from cache clearing code in Notice --- classes/Notice.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index c8edc98e1a..d651ef80ed 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -498,9 +498,6 @@ class Notice extends Memcached_DataObject $original->free(); unset($original); } - - $ni->free(); - unset($ni); } } } From 8bfa7fdeaf85091907e5c0c9f181655a9a5dcdf5 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 00:47:12 -0800 Subject: [PATCH 42/57] repeats don't get posted to groups --- classes/Notice.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index d651ef80ed..f48e77c1d2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -919,6 +919,12 @@ class Notice extends Memcached_DataObject function saveGroups() { + // Don't save groups for repeats + + if (!empty($this->repeat_of)) { + return array(); + } + $groups = array(); /* extract all !group */ From 43ff54218934663910f11762ee2a1c1083b119c7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 01:13:06 -0800 Subject: [PATCH 43/57] Don't save reply info for repeats --- classes/Notice.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index f48e77c1d2..02cd20391a 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -275,7 +275,6 @@ class Notice extends Memcached_DataObject if (isset($repeat_of)) { $notice->repeat_of = $repeat_of; - $notice->reply_to = $repeat_of; } else { $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); } @@ -995,6 +994,12 @@ class Notice extends Memcached_DataObject */ function saveReplies() { + // Don't save reply data for repeats + + if (!empty($this->repeat_of)) { + return array(); + } + // Alternative reply format $tname = false; if (preg_match('/^T ([A-Z0-9]{1,64}) /', $this->content, $match)) { From 23599da91e9d4abbd6e73ef3f44c58f302a5231a Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 01:13:49 -0800 Subject: [PATCH 44/57] refactor jabber broadcast for notice_inbox removal --- lib/jabber.php | 93 ++++++++++++++++---------------------------------- 1 file changed, 29 insertions(+), 64 deletions(-) diff --git a/lib/jabber.php b/lib/jabber.php index 1d0bb94231..69f0d3570c 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -356,77 +356,42 @@ function jabber_broadcast_notice($notice) $conn = jabber_connect(); - // First, get users to whom this is a direct reply - $user = new User(); - $UT = common_config('db','type')=='pgsql'?'"user"':'user'; - $user->query("SELECT $UT.id, $UT.jabber " . - "FROM $UT JOIN reply ON $UT.id = reply.profile_id " . - 'WHERE reply.notice_id = ' . $notice->id . ' ' . - "AND $UT.jabber is not null " . - "AND $UT.jabbernotify = 1 " . - "AND $UT.jabberreplies = 1 "); + $ni = $notice->whoGets(); + + foreach ($ni as $user_id => $reason) { + $user = User::staticGet('user_id', $user_id); + if (empty($user) || + empty($user->jabber) || + !$user->jabbernotify) { + // either not a local user, or just not found + continue; + } + switch ($reason) { + case NOTICE_INBOX_SOURCE_REPLY: + if (!$user->jabberreplies) { + continue; + } + break; + case NOTICE_INBOX_SOURCE_SUB: + $sub = Subscription::pkeyGet(array('subscriber' => $user->id, + 'subscribed' => $notice->profile_id)); + if (empty($sub) || !$sub->jabber) { + continue; + } + break; + case NOTICE_INBOX_SOURCE_GROUP: + break; + default: + throw new Exception(_("Unknown inbox source.")); + } - while ($user->fetch()) { common_log(LOG_INFO, - 'Sending reply notice ' . $notice->id . ' to ' . $user->jabber, + 'Sending notice ' . $notice->id . ' to ' . $user->jabber, __FILE__); $conn->message($user->jabber, $msg, 'chat', null, $entry); $conn->processTime(0); - $sent_to[$user->id] = 1; } - $user->free(); - - // Now, get users subscribed to this profile - - $user = new User(); - $user->query("SELECT $UT.id, $UT.jabber " . - "FROM $UT JOIN subscription " . - "ON $UT.id = subscription.subscriber " . - 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' . - "AND $UT.jabber is not null " . - "AND $UT.jabbernotify = 1 " . - 'AND subscription.jabber = 1 '); - - while ($user->fetch()) { - if (!array_key_exists($user->id, $sent_to)) { - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . ' to ' . $user->jabber, - __FILE__); - $conn->message($user->jabber, $msg, 'chat', null, $entry); - // To keep the incoming queue from filling up, - // we service it after each send. - $conn->processTime(0); - $sent_to[$user->id] = 1; - } - } - - // Now, get users who have it in their inbox because of groups - - $user = new User(); - $user->query("SELECT $UT.id, $UT.jabber " . - "FROM $UT JOIN notice_inbox " . - "ON $UT.id = notice_inbox.user_id " . - 'WHERE notice_inbox.notice_id = ' . $notice->id . ' ' . - 'AND notice_inbox.source = 2 ' . - "AND $UT.jabber is not null " . - "AND $UT.jabbernotify = 1 "); - - while ($user->fetch()) { - if (!array_key_exists($user->id, $sent_to)) { - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . ' to ' . $user->jabber, - __FILE__); - $conn->message($user->jabber, $msg, 'chat', null, $entry); - // To keep the incoming queue from filling up, - // we service it after each send. - $conn->processTime(0); - $sent_to[$user->id] = 1; - } - } - - $user->free(); - return true; } From 430bd69312a27f9f97bda78566a78c9f7eec1f14 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 02:16:13 -0800 Subject: [PATCH 45/57] add events for subscribing to people and joining groups --- EVENTS.txt | 32 ++++++++++++++ actions/joingroup.php | 16 +++---- actions/leavegroup.php | 21 ++++------ classes/Group_member.php | 37 +++++++++++++++++ lib/command.php | 45 ++++++++------------ lib/subs.php | 90 ++++++++++++++++++++++++---------------- 6 files changed, 154 insertions(+), 87 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index 64e345b692..e6400244e4 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -655,3 +655,35 @@ StartUnblockProfile: when we're about to unblock EndUnblockProfile: when an unblock has succeeded - $user: the person doing the unblock - $profile: the person unblocked, can be remote + +StartSubscribe: when a subscription is starting +- $user: the person subscribing +- $other: the person being subscribed to + +EndSubscribe: when a subscription is finished +- $user: the person subscribing +- $other: the person being subscribed to + +StartUnsubscribe: when an unsubscribe is starting +- $user: the person unsubscribing +- $other: the person being unsubscribed from + +EndUnsubscribe: when an unsubscribe is done +- $user: the person unsubscribing +- $other: the person being unsubscribed to + +StartJoinGroup: when a user is joining a group +- $group: the group being joined +- $user: the user joining + +EndJoinGroup: when a user finishes joining a group +- $group: the group being joined +- $user: the user joining + +StartLeaveGroup: when a user is leaving a group +- $group: the group being left +- $user: the user leaving + +EndLeaveGroup: when a user has left a group +- $group: the group being left +- $user: the user leaving diff --git a/actions/joingroup.php b/actions/joingroup.php index 05e33e7cb1..235e5ab4c2 100644 --- a/actions/joingroup.php +++ b/actions/joingroup.php @@ -115,16 +115,12 @@ class JoingroupAction extends Action $cur = common_current_user(); - $member = new Group_member(); - - $member->group_id = $this->group->id; - $member->profile_id = $cur->id; - $member->created = common_sql_now(); - - $result = $member->insert(); - - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); + try { + if (Event::handle('StartJoinGroup', array($this->group, $cur))) { + Group_member::join($this->group->id, $cur->id); + Event::handle('EndJoinGroup', array($this->group, $cur)); + } + } catch (Exception $e) { $this->serverError(sprintf(_('Could not join user %1$s to group %2$s.'), $cur->nickname, $this->group->nickname)); } diff --git a/actions/leavegroup.php b/actions/leavegroup.php index b0f973e1ac..9b9d83b6ca 100644 --- a/actions/leavegroup.php +++ b/actions/leavegroup.php @@ -110,22 +110,15 @@ class LeavegroupAction extends Action $cur = common_current_user(); - $member = new Group_member(); - - $member->group_id = $this->group->id; - $member->profile_id = $cur->id; - - if (!$member->find(true)) { - $this->serverError(_('Could not find membership record.')); - return; - } - - $result = $member->delete(); - - if (!$result) { - common_log_db_error($member, 'DELETE', __FILE__); + try { + if (Event::handle('StartLeaveGroup', array($this->group, $cur))) { + Group_member::leave($this->group->id, $cur->id); + Event::handle('EndLeaveGroup', array($this->group, $cur)); + } + } catch (Exception $e) { $this->serverError(sprintf(_('Could not remove user %1$s from group %2$s.'), $cur->nickname, $this->group->nickname)); + return; } if ($this->boolean('ajax')) { diff --git a/classes/Group_member.php b/classes/Group_member.php index 069b2c7a1c..7b1760f767 100644 --- a/classes/Group_member.php +++ b/classes/Group_member.php @@ -25,4 +25,41 @@ class Group_member extends Memcached_DataObject { return Memcached_DataObject::pkeyGet('Group_member', $kv); } + + static function join($group_id, $profile_id) + { + $member = new Group_member(); + + $member->group_id = $group_id; + $member->profile_id = $profile_id; + $member->created = common_sql_now(); + + $result = $member->insert(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + throw new Exception(_("Group join failed.")); + } + + return true; + } + + static function leave($group_id, $profile_id) + { + $member = Group_member::pkeyGet(array('group_id' => $group_id, + 'profile_id' => $profile_id)); + + if (empty($member)) { + throw new Exception(_("Not part of group.")); + } + + $result = $member->delete(); + + if (!$result) { + common_log_db_error($member, 'INSERT', __FILE__); + throw new Exception(_("Group leave failed.")); + } + + return true; + } } diff --git a/lib/command.php b/lib/command.php index f846fb823f..c0a32e1b1a 100644 --- a/lib/command.php +++ b/lib/command.php @@ -222,18 +222,15 @@ class JoinCommand extends Command return; } - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $cur->id; - $member->created = common_sql_now(); - - $result = $member->insert(); - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); - $channel->error($cur, sprintf(_('Could not join user %s to group %s'), - $cur->nickname, $group->nickname)); - return; + try { + if (Event::handle('StartJoinGroup', array($group, $cur))) { + Group_member::join($group->id, $cur->id); + Event::handle('EndJoinGroup', array($group, $cur)); + } + } catch (Exception $e) { + $channel->error($cur, sprintf(_('Could not join user %s to group %s'), + $cur->nickname, $group->nickname)); + return; } $channel->output($cur, sprintf(_('%s joined group %s'), @@ -269,21 +266,15 @@ class DropCommand extends Command return; } - $member = new Group_member(); - - $member->group_id = $group->id; - $member->profile_id = $cur->id; - - if (!$member->find(true)) { - $channel->error($cur,_('Could not find membership record.')); - return; - } - $result = $member->delete(); - if (!$result) { - common_log_db_error($member, 'INSERT', __FILE__); - $channel->error($cur, sprintf(_('Could not remove user %s to group %s'), - $cur->nickname, $group->nickname)); - return; + try { + if (Event::handle('StartLeaveGroup', array($group, $cur))) { + Group_member::leave($group->id, $cur->id); + Event::handle('EndLeaveGroup', array($group, $cur)); + } + } catch (Exception $e) { + $channel->error($cur, sprintf(_('Could not remove user %s to group %s'), + $cur->nickname, $group->nickname)); + return; } $channel->output($cur, sprintf(_('%s left group %s'), diff --git a/lib/subs.php b/lib/subs.php index 4b6b03967a..5ac1a75a5c 100644 --- a/lib/subs.php +++ b/lib/subs.php @@ -56,35 +56,44 @@ function subs_subscribe_to($user, $other) return _('User has blocked you.'); } - if (!$user->subscribeTo($other)) { - return _('Could not subscribe.'); - return; - } + try { + if (Event::handle('StartSubscribe', array($user, $other))) { - subs_notify($other, $user); + if (!$user->subscribeTo($other)) { + return _('Could not subscribe.'); + return; + } - $cache = common_memcache(); + subs_notify($other, $user); - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - } + $cache = common_memcache(); - $profile = $user->getProfile(); + if ($cache) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); + } - $profile->blowSubscriptionsCount(); - $other->blowSubscribersCount(); + $profile = $user->getProfile(); - if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) { - if (!$other->subscribeTo($user)) { - return _('Could not subscribe other to you.'); + $profile->blowSubscriptionsCount(); + $other->blowSubscribersCount(); + + if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) { + if (!$other->subscribeTo($user)) { + return _('Could not subscribe other to you.'); + } + $cache = common_memcache(); + + if ($cache) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id)); + } + + subs_notify($user, $other); + } + + Event::handle('EndSubscribe', array($user, $other)); } - $cache = common_memcache(); - - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id)); - } - - subs_notify($user, $other); + } catch (Exception $e) { + return $e->getMessage(); } return true; @@ -133,28 +142,37 @@ function subs_unsubscribe_to($user, $other) return _('Couldn\'t delete self-subscription.'); } - $sub = DB_DataObject::factory('subscription'); + try { + if (Event::handle('StartUnsubscribe', array($user, $other))) { - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; + $sub = DB_DataObject::factory('subscription'); - $sub->find(true); + $sub->subscriber = $user->id; + $sub->subscribed = $other->id; - // note we checked for existence above + $sub->find(true); - if (!$sub->delete()) - return _('Couldn\'t delete subscription.'); + // note we checked for existence above - $cache = common_memcache(); + if (!$sub->delete()) + return _('Couldn\'t delete subscription.'); - if ($cache) { - $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); - } + $cache = common_memcache(); - $profile = $user->getProfile(); + if ($cache) { + $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); + } - $profile->blowSubscriptionsCount(); - $other->blowSubscribersCount(); + $profile = $user->getProfile(); + + $profile->blowSubscriptionsCount(); + $other->blowSubscribersCount(); + + Event::handle('EndUnsubscribe', array($user, $other)); + } + } catch (Exception $e) { + return $e->getMessage(); + } return true; } From 601c3713320c0fe4c4d9f97b486d279bb6884de9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 03:01:22 -0800 Subject: [PATCH 46/57] correct check for error in subscribe and unsubscribe actions --- actions/subscribe.php | 2 +- actions/unsubscribe.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/subscribe.php b/actions/subscribe.php index 4c46806e40..a90d7facdf 100644 --- a/actions/subscribe.php +++ b/actions/subscribe.php @@ -58,7 +58,7 @@ class SubscribeAction extends Action $result = subs_subscribe_to($user, $other); - if($result != true) { + if (is_string($result)) { $this->clientError($result); return; } diff --git a/actions/unsubscribe.php b/actions/unsubscribe.php index dbb4e41538..6bb10d448b 100644 --- a/actions/unsubscribe.php +++ b/actions/unsubscribe.php @@ -87,7 +87,7 @@ class UnsubscribeAction extends Action $result = subs_unsubscribe_to($user, $other); - if ($result != true) { + if (is_string($result)) { $this->clientError($result); return; } From f2e9469e1600bbb350ec170e6ac14ad6cd724d23 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 03:11:31 -0800 Subject: [PATCH 47/57] throttle subscriptions --- plugins/SubscriptionThrottlePlugin.php | 175 +++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 plugins/SubscriptionThrottlePlugin.php diff --git a/plugins/SubscriptionThrottlePlugin.php b/plugins/SubscriptionThrottlePlugin.php new file mode 100644 index 0000000000..114113360e --- /dev/null +++ b/plugins/SubscriptionThrottlePlugin.php @@ -0,0 +1,175 @@ +. + * + * @category Throttle + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Subscription throttle + * + * @category Throttle + * @package StatusNet + * @author Evan Prodromou + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class SubscriptionThrottlePlugin extends Plugin +{ + public $subLimits = array(86400 => 100, + 3600 => 50); + + public $groupLimits = array(86400 => 50, + 3600 => 25); + + /** + * Filter subscriptions to see if they're coming too fast. + * + * @param User $user The user subscribing + * @param User $other The user being subscribed to + * + * @return boolean hook value + */ + + function onStartSubscribe($user, $other) + { + foreach ($this->subLimits as $seconds => $limit) { + $sub = $this->_getNthSub($user, $limit); + + if (!empty($sub)) { + $subtime = strtotime($sub->created); + $now = time(); + if ($now - $subtime < $seconds) { + throw new Exception(_("Too many subscriptions. Take a break and try again later.")); + } + } + } + + return true; + } + + /** + * Filter group joins to see if they're coming too fast. + * + * @param Group $group The group being joined + * @param User $user The user joining + * + * @return boolean hook value + */ + + function onStartJoinGroup($group, $user) + { + foreach ($this->groupLimits as $seconds => $limit) { + $mem = $this->_getNthMem($user, $limit); + if (!empty($mem)) { + + $jointime = strtotime($mem->created); + $now = time(); + if ($now - $jointime < $seconds) { + throw new Exception(_("Too many memberships. Take a break and try again later.")); + } + } + } + + return true; + } + + /** + * Get the Nth most recent subscription for this user + * + * @param User $user The user to get subscriptions for + * @param integer $n How far to count back + * + * @return Subscription a subscription or null + */ + + private function _getNthSub($user, $n) + { + $sub = new Subscription(); + + $sub->subscriber = $user->id; + $sub->orderBy('created DESC'); + $sub->limit($n - 1, 1); + + if ($sub->find(true)) { + return $sub; + } else { + return null; + } + } + + /** + * Get the Nth most recent group membership for this user + * + * @param User $user The user to get memberships for + * @param integer $n How far to count back + * + * @return Group_member a membership or null + */ + + private function _getNthMem($user, $n) + { + $mem = new Group_member(); + + $mem->profile_id = $user->id; + $mem->orderBy('created DESC'); + $mem->limit($n - 1, 1); + + if ($mem->find(true)) { + return $mem; + } else { + return null; + } + } + + /** + * Return plugin version data for display + * + * @param array &$versions Array of version arrays + * + * @return boolean hook value + */ + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'SubscriptionThrottle', + 'version' => STATUSNET_VERSION, + 'author' => 'Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:SubscriptionThrottle', + 'rawdescription' => + _m('Configurable limits for subscriptions and group memberships.')); + return true; + } +} + From 9b2fdefe3925ca807ccf80339bef019306096cc6 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 15:21:43 +0000 Subject: [PATCH 48/57] Using a JSON object for NoticeDataGeo text instead --- js/util.js | 4 ++-- lib/noticeform.php | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/util.js b/js/util.js index 43f4922749..0ee3d53bd5 100644 --- a/js/util.js +++ b/js/util.js @@ -567,7 +567,7 @@ var SN = { // StatusNet } if ($('#'+SN.C.S.NoticeDataGeo).attr('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === null) { - $('label[for='+SN.C.S.NoticeDataGeo+']').addClass('checked').attr('title', NoticeDataGeoShareDisable_text); + $('label[for='+SN.C.S.NoticeDataGeo+']').addClass('checked').attr('title', NoticeDataGeo_text.ShareDisable); var S = '
'; var NDGS = $('#'+SN.C.S.NoticeDataGeoSelected); @@ -580,7 +580,7 @@ var SN = { // StatusNet } NDGS = $('#'+SN.C.S.NoticeDataGeoSelected); - NDGS.prepend('Geo '); + NDGS.prepend('Geo '); var NLN = $('#'+SN.C.S.NoticeGeoName); NLN.addClass('processing'); diff --git a/lib/noticeform.php b/lib/noticeform.php index 02e35a8d79..41504b7356 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -211,8 +211,10 @@ class NoticeForm extends Form 'title' => common_local_url('geocode'))); $this->out->checkbox('notice_data-geo', _('Share my location'), true); $this->out->elementEnd('div'); - $this->out->inlineScript(' var NoticeDataGeoShareDisable_text = "'._('Do not share my location').'";'. - ' var NoticeDataGeoInfoMinimize_text = "'._('Hide this info').'";'); + $this->out->inlineScript(' var NoticeDataGeo_text = {'. + 'ShareDisable: "'._('Do not share my location').'",'. + 'InfoMinimize: "'._('Hide this info').'"'. + '}'); } Event::handle('EndShowNoticeFormData', array($this)); From 2000d2d36b466615e3867f0002ddeaf793cf666f Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 15:36:42 +0000 Subject: [PATCH 49/57] Added timeout error handling for geo location --- js/util.js | 13 +++++++++++-- lib/noticeform.php | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index 0ee3d53bd5..b56410e62f 100644 --- a/js/util.js +++ b/js/util.js @@ -632,9 +632,18 @@ var SN = { // StatusNet }, function(error) { - if (error.PERMISSION_DENIED == 1) { - removeNoticeDataGeo(); + switch(error.code) { + case error.PERMISSION_DENIED: + removeNoticeDataGeo(); + break; + case error.TIMEOUT: + $('#'+SN.C.S.NoticeGeoName).text(NoticeDataGeo_text.ErrorTimeout).removeClass('processing'); + break; } + }, + + { + timeout: 10000 } ); } diff --git a/lib/noticeform.php b/lib/noticeform.php index 41504b7356..a00615ce79 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -213,7 +213,8 @@ class NoticeForm extends Form $this->out->elementEnd('div'); $this->out->inlineScript(' var NoticeDataGeo_text = {'. 'ShareDisable: "'._('Do not share my location').'",'. - 'InfoMinimize: "'._('Hide this info').'"'. + 'InfoMinimize: "'._('Hide this info').'",'. + 'ErrorTimeout: "'._('Sorry, retrieving your geo location is taking longer than expected, please try again later').'"'. '}'); } From 8887d7a314bdbf4e4fc6ca26addf5ce28284d697 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 15:48:26 +0000 Subject: [PATCH 50/57] Unchecks location sharing for geo location if it timesout --- js/util.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index b56410e62f..e269798d89 100644 --- a/js/util.js +++ b/js/util.js @@ -638,12 +638,13 @@ var SN = { // StatusNet break; case error.TIMEOUT: $('#'+SN.C.S.NoticeGeoName).text(NoticeDataGeo_text.ErrorTimeout).removeClass('processing'); + $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); break; } }, { - timeout: 10000 + timeout: 10 } ); } From af97a14f7cbe9a5b28ba3371c0e0324ce5c5d691 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 15:50:45 +0000 Subject: [PATCH 51/57] Timeout should be 10000, not 10 Revert "Unchecks location sharing for geo location if it timesout" This reverts commit 8887d7a314bdbf4e4fc6ca26addf5ce28284d697. --- js/util.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/util.js b/js/util.js index e269798d89..b56410e62f 100644 --- a/js/util.js +++ b/js/util.js @@ -638,13 +638,12 @@ var SN = { // StatusNet break; case error.TIMEOUT: $('#'+SN.C.S.NoticeGeoName).text(NoticeDataGeo_text.ErrorTimeout).removeClass('processing'); - $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); break; } }, { - timeout: 10 + timeout: 10000 } ); } From d84c33c3282523ea3553fa36ccd2aef39e4dcbad Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 15:51:32 +0000 Subject: [PATCH 52/57] Unchecks location sharing for geo location if it timesout --- js/util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/js/util.js b/js/util.js index b56410e62f..2cff4afc20 100644 --- a/js/util.js +++ b/js/util.js @@ -638,6 +638,7 @@ var SN = { // StatusNet break; case error.TIMEOUT: $('#'+SN.C.S.NoticeGeoName).text(NoticeDataGeo_text.ErrorTimeout).removeClass('processing'); + $('#'+SN.C.S.NoticeDataGeo).attr('checked', false); break; } }, From 0e1f2d4b47e5e340679c4245b62e1d64c6b9c9b9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 13 Jan 2010 18:17:36 +0000 Subject: [PATCH 53/57] Fix regressions in XMPP output during inbox refactoring - NOTICE_INBOX_SOURCE_* constants moved to common.php since Notice_inbox.php not always loaded - fixed typo in User::staticGet() call which caused user #1 to receive messages once for each subscriber instead of for him/herself - 'continue' -> 'continue 2' inside switch() statement to fix loop escape (PHP considers switch() a looping construct for break & continue) --- classes/Notice_inbox.php | 6 ------ lib/common.php | 6 ++++++ lib/jabber.php | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php index 6c328e6854..c27dcdfd61 100644 --- a/classes/Notice_inbox.php +++ b/classes/Notice_inbox.php @@ -31,12 +31,6 @@ define('NOTICE_INBOX_GC_MAX', 12800); define('NOTICE_INBOX_LIMIT', 1000); define('NOTICE_INBOX_SOFT_LIMIT', 1000); -define('NOTICE_INBOX_SOURCE_SUB', 1); -define('NOTICE_INBOX_SOURCE_GROUP', 2); -define('NOTICE_INBOX_SOURCE_REPLY', 3); -define('NOTICE_INBOX_SOURCE_FORWARD', 4); -define('NOTICE_INBOX_SOURCE_GATEWAY', -1); - class Notice_inbox extends Memcached_DataObject { ###START_AUTOCODE diff --git a/lib/common.php b/lib/common.php index 61decebb7a..00e80373ee 100644 --- a/lib/common.php +++ b/lib/common.php @@ -41,6 +41,12 @@ define('FOREIGN_NOTICE_SEND_REPLY', 4); define('FOREIGN_FRIEND_SEND', 1); define('FOREIGN_FRIEND_RECV', 2); +define('NOTICE_INBOX_SOURCE_SUB', 1); +define('NOTICE_INBOX_SOURCE_GROUP', 2); +define('NOTICE_INBOX_SOURCE_REPLY', 3); +define('NOTICE_INBOX_SOURCE_FORWARD', 4); +define('NOTICE_INBOX_SOURCE_GATEWAY', -1); + # append our extlib dir as the last-resort place to find libs set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/'); diff --git a/lib/jabber.php b/lib/jabber.php index 69f0d3570c..4cdfa67465 100644 --- a/lib/jabber.php +++ b/lib/jabber.php @@ -359,7 +359,7 @@ function jabber_broadcast_notice($notice) $ni = $notice->whoGets(); foreach ($ni as $user_id => $reason) { - $user = User::staticGet('user_id', $user_id); + $user = User::staticGet($user_id); if (empty($user) || empty($user->jabber) || !$user->jabbernotify) { @@ -369,20 +369,20 @@ function jabber_broadcast_notice($notice) switch ($reason) { case NOTICE_INBOX_SOURCE_REPLY: if (!$user->jabberreplies) { - continue; + continue 2; } break; case NOTICE_INBOX_SOURCE_SUB: $sub = Subscription::pkeyGet(array('subscriber' => $user->id, 'subscribed' => $notice->profile_id)); if (empty($sub) || !$sub->jabber) { - continue; + continue 2; } break; case NOTICE_INBOX_SOURCE_GROUP: break; default: - throw new Exception(_("Unknown inbox source.")); + throw new Exception(sprintf(_("Unknown inbox source %d."), $reason)); } common_log(LOG_INFO, From 43170b3d18153b3dfd8675bd77ae1133eed8148a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 13 Jan 2010 19:15:54 +0000 Subject: [PATCH 54/57] When Twitter bridge encounters a 403 (rate limit) err, drop the notice instead of requeuing. --- plugins/TwitterBridge/twitter.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index e133ce6f74..33dfb788bf 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -269,19 +269,23 @@ function process_error($e, $flink, $notice) common_log(LOG_WARNING, $logmsg); - if ($code == 401) { - + switch($code) { + case 401: // Probably a revoked or otherwise bad access token - nuke! - remove_twitter_link($flink); return true; - - } else { + break; + case 403: + // User has exceeder her rate limit -- toss the notice + return true; + break; + default: // For every other case, it's probably some flakiness so try // sending the notice again later (requeue). return false; + break; } } From 681065d7342f209800014a54e400a687f615640c Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 13 Jan 2010 15:35:53 -0500 Subject: [PATCH 55/57] Made the IMAP plugin work in the style of the new queue/iomanagers --- plugins/Imap/ImapPlugin.php | 38 ++++---- plugins/Imap/imapdaemon.php | 147 ------------------------------- plugins/Imap/imapmailhandler.php | 32 +++++++ plugins/Imap/imapmanager.php | 129 +++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 162 deletions(-) delete mode 100755 plugins/Imap/imapdaemon.php create mode 100644 plugins/Imap/imapmailhandler.php create mode 100644 plugins/Imap/imapmanager.php diff --git a/plugins/Imap/ImapPlugin.php b/plugins/Imap/ImapPlugin.php index d9768b6802..89a775a16a 100644 --- a/plugins/Imap/ImapPlugin.php +++ b/plugins/Imap/ImapPlugin.php @@ -46,8 +46,6 @@ class ImapPlugin extends Plugin public $user; public $password; public $poll_frequency = 60; - public static $instances = array(); - public static $daemon_added = array(); function initialize(){ if(!isset($this->mailbox)){ @@ -63,24 +61,34 @@ class ImapPlugin extends Plugin throw new Exception("must specify a poll_frequency"); } - self::$instances[] = $this; return true; } - function cleanup(){ - $index = array_search($this, self::$instances); - unset(self::$instances[$index]); - return true; - } - - function onGetValidDaemons($daemons) + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) { - if(! self::$daemon_added){ - array_push($daemons, INSTALLDIR . - '/plugins/Imap/imapdaemon.php'); - self::$daemon_added = true; + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'ImapManager': + case 'IMAPMailHandler': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + default: + return true; } - return true; + } + + function onStartIoManagerClasses(&$classes) + { + $classes[] = new ImapManager($this); } function onPluginVersion(&$versions) diff --git a/plugins/Imap/imapdaemon.php b/plugins/Imap/imapdaemon.php deleted file mode 100755 index 7e60e13763..0000000000 --- a/plugins/Imap/imapdaemon.php +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); - -$shortoptions = 'fi::'; -$longoptions = array('id::', 'foreground'); - -$helptext = <<$value) - { - $this->$attr = $value; - } - - $this->log(LOG_INFO, "INITIALIZE IMAPDaemon {" . $this->name() . "}"); - } - - function name() - { - return strtolower('imapdaemon.'.$this->user.'.'.crc32($this->mailbox)); - } - - function run() - { - $this->connect(); - while(true) - { - if(imap_ping($this->conn) || $this->connect()) - { - $this->check_mailbox(); - } - sleep($this->poll_frequency); - } - } - - function check_mailbox() - { - $count = imap_num_msg($this->conn); - $this->log(LOG_INFO, "Found $count messages"); - if($count > 0){ - $handler = new IMAPMailHandler(); - for($i=1; $i <= $count; $i++) - { - $rawmessage = imap_fetchheader($this->conn, $count, FT_PREFETCHTEXT) . imap_body($this->conn, $i); - $handler->handle_message($rawmessage); - imap_delete($this->conn, $i); - } - imap_expunge($this->conn); - $this->log(LOG_INFO, "Finished processing messages"); - } - } - - function log($level, $msg) - { - $text = $this->name() . ': '.$msg; - common_log($level, $text); - if (!$this->daemonize) - { - $line = common_log_line($level, $text); - echo $line; - echo "\n"; - } - } - - function connect() - { - $this->conn = imap_open($this->mailbox, $this->user, $this->password); - if($this->conn){ - $this->log(LOG_INFO, "Connected"); - return true; - }else{ - $this->log(LOG_INFO, "Failed to connect: " . imap_last_error()); - return false; - } - } -} - -class IMAPMailHandler extends MailHandler -{ - function error($from, $msg) - { - $this->log(LOG_INFO, "Error: $from $msg"); - $headers['To'] = $from; - $headers['Subject'] = _m('Error'); - - return mail_send(array($from), $headers, $msg); - } -} - -if (have_option('i', 'id')) { - $id = get_option_value('i', 'id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$foreground = have_option('f', 'foreground'); - -foreach(ImapPlugin::$instances as $pluginInstance){ - - $daemon = new IMAPDaemon($id, !$foreground, array( - 'mailbox' => $pluginInstance->mailbox, - 'user' => $pluginInstance->user, - 'password' => $pluginInstance->password, - 'poll_frequency' => $pluginInstance->poll_frequency - )); - - $daemon->runOnce(); - -} diff --git a/plugins/Imap/imapmailhandler.php b/plugins/Imap/imapmailhandler.php new file mode 100644 index 0000000000..3d4b6113a9 --- /dev/null +++ b/plugins/Imap/imapmailhandler.php @@ -0,0 +1,32 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class IMAPMailHandler extends MailHandler +{ + function error($from, $msg) + { + $this->log(LOG_INFO, "Error: $from $msg"); + $headers['To'] = $from; + $headers['Subject'] = _m('Error'); + + return mail_send(array($from), $headers, $msg); + } +} diff --git a/plugins/Imap/imapmanager.php b/plugins/Imap/imapmanager.php new file mode 100644 index 0000000000..e4fda58099 --- /dev/null +++ b/plugins/Imap/imapmanager.php @@ -0,0 +1,129 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009-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/ + */ + +class ImapManager extends IoManager +{ + protected $conn = null; + + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Fetch the singleton manager for the current site. + * @return mixed ImapManager, or false if unneeded + */ + public static function get() + { + throw new Exception('ImapManager should be created using it\'s constructor, not the static get method'); + } + + /** + * Lists the IM connection socket to allow i/o master to wake + * when input comes in here as well as from the queue source. + * + * @return array of resources + */ + public function getSockets() + { + return array(); + } + + /** + * Tell the i/o master we need one instance for each supporting site + * being handled in this process. + */ + public static function multiSite() + { + return IoManager::INSTANCE_PER_SITE; + } + + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->conn = $this->connect(); + return true; + }else{ + return false; + } + } + + public function handleInput($socket) + { + $this->check_mailbox(); + return true; + } + + public function poll() + { + return $this->check_mailbox() > 0; + } + + function pollInterval() + { + return $this->plugin->poll_frequency; + } + + protected function connect() + { + $this->conn = imap_open($this->plugin->mailbox, $this->plugin->user, $this->plugin->password); + if($this->conn){ + common_log(LOG_INFO, "Connected"); + return $this->conn; + }else{ + common_log(LOG_INFO, "Failed to connect: " . imap_last_error()); + return $this->conn; + } + } + + protected function check_mailbox() + { + imap_ping($this->conn); + $count = imap_num_msg($this->conn); + common_log(LOG_INFO, "Found $count messages"); + if($count > 0){ + $handler = new IMAPMailHandler(); + for($i=1; $i <= $count; $i++) + { + $rawmessage = imap_fetchheader($this->conn, $count, FT_PREFETCHTEXT) . imap_body($this->conn, $i); + $handler->handle_message($rawmessage); + imap_delete($this->conn, $i); + } + imap_expunge($this->conn); + common_log(LOG_INFO, "Finished processing messages"); + } + return $count; + } +} From d032fc038a32c142a6156b1b33ecd6e09ab4764c Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 13 Jan 2010 12:37:01 -0800 Subject: [PATCH 56/57] make sure whoGets() doesn't write anything --- classes/Notice.php | 69 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 02cd20391a..a43ce867b5 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -335,7 +335,11 @@ class Notice extends Memcached_DataObject $notice->saveTags(); - $notice->addToInboxes(); + $groups = $notice->saveGroups(); + + $recipients = $notice->saveReplies(); + + $notice->addToInboxes($groups, $recipients); $notice->saveUrls(); @@ -822,7 +826,7 @@ class Notice extends Memcached_DataObject return $ids; } - function whoGets() + function whoGets($groups=null, $recipients=null) { $c = self::memcache(); @@ -833,6 +837,14 @@ class Notice extends Memcached_DataObject } } + if (is_null($groups)) { + $groups = $this->getGroups(); + } + + if (is_null($recipients)) { + $recipients = $this->getReplies(); + } + $users = $this->getSubscribedUsers(); // FIXME: kind of ignoring 'transitional'... @@ -845,7 +857,6 @@ class Notice extends Memcached_DataObject $ni[$id] = NOTICE_INBOX_SOURCE_SUB; } - $groups = $this->saveGroups(); $profile = $this->getProfile(); foreach ($groups as $group) { @@ -860,8 +871,6 @@ class Notice extends Memcached_DataObject } } - $recipients = $this->saveReplies(); - foreach ($recipients as $recipient) { if (!array_key_exists($recipient, $ni)) { @@ -880,9 +889,9 @@ class Notice extends Memcached_DataObject return $ni; } - function addToInboxes() + function addToInboxes($groups, $recipients) { - $ni = $this->whoGets(); + $ni = $this->whoGets($groups, $recipients); Inbox::bulkInsert($this->id, array_keys($ni)); @@ -1086,6 +1095,52 @@ class Notice extends Memcached_DataObject return $recipientIds; } + function getReplies() + { + // XXX: cache me + + $ids = array(); + + $reply = new Reply(); + $reply->selectAdd(); + $reply->selectAdd('profile_id'); + $reply->notice_id = $this->id; + + if ($reply->find()) { + while($reply->fetch()) { + $ids[] = $reply->profile_id; + } + } + + $reply->free(); + + return $ids; + } + + function getGroups() + { + // XXX: cache me + + $ids = array(); + + $gi = new Group_inbox(); + + $gi->selectAdd(); + $gi->selectAdd('group_id'); + + $gi->notice_id = $this->id; + + if ($gi->find()) { + while ($gi->fetch()) { + $ids[] = $gi->group_id; + } + } + + $gi->free(); + + return $ids; + } + function asAtomEntry($namespace=false, $source=false) { $profile = $this->getProfile(); From 5d88e6435f5b6725c09480a7d7cd4a8a80a62202 Mon Sep 17 00:00:00 2001 From: Sarven Capadisli Date: Wed, 13 Jan 2010 21:07:01 +0000 Subject: [PATCH 57/57] Changed to margin instead of list position in order to avoid text wrapping from the bullet point --- theme/base/css/display.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theme/base/css/display.css b/theme/base/css/display.css index cdacb9a629..e89661637e 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1502,7 +1502,7 @@ width:auto; .system_notice ul, .instructions ul { -list-style-position:inside; +margin-left:1em; } .instructions p, .instructions ul {