Merge branch 'MDL-45029-master' of git://github.com/lameze/moodle
authorDamyon Wiese <damyon@moodle.com>
Wed, 20 Aug 2014 02:21:20 +0000 (10:21 +0800)
committerDamyon Wiese <damyon@moodle.com>
Wed, 20 Aug 2014 02:21:20 +0000 (10:21 +0800)
152 files changed:
admin/auth_config.php
auth/cas/CAS/CAS.php
auth/cas/CAS/CAS/AuthenticationException.php
auth/cas/CAS/CAS/Autoload.php
auth/cas/CAS/CAS/Client.php
auth/cas/CAS/CAS/CookieJar.php
auth/cas/CAS/CAS/Languages/German.php
auth/cas/CAS/CAS/OutOfSequenceBeforeAuthenticationCallException.php [new file with mode: 0644]
auth/cas/CAS/CAS/OutOfSequenceBeforeClientException.php [new file with mode: 0644]
auth/cas/CAS/CAS/OutOfSequenceBeforeProxyException.php [new file with mode: 0644]
auth/cas/CAS/CAS/PGTStorage/AbstractStorage.php
auth/cas/CAS/CAS/PGTStorage/Db.php
auth/cas/CAS/CAS/ProxiedService/Abstract.php
auth/cas/CAS/CAS/ProxiedService/Http/Abstract.php
auth/cas/CAS/CAS/ProxiedService/Http/Get.php
auth/cas/CAS/CAS/ProxiedService/Http/Post.php
auth/cas/CAS/CAS/ProxiedService/Imap.php
auth/cas/CAS/CAS/ProxiedService/Testable.php
auth/cas/CAS/CAS/ProxyChain.php
auth/cas/CAS/CAS/ProxyTicketException.php
auth/cas/CAS/CAS/Request/AbstractRequest.php
auth/cas/CAS/CAS/Request/CurlMultiRequest.php
auth/cas/CAS/CAS/Request/CurlRequest.php
auth/cas/CAS/CAS/TypeMismatchException.php [new file with mode: 0644]
auth/cas/CAS/moodle_readme.txt
auth/cas/thirdpartylibs.xml
backup/moodle2/backup_xml_transformer.class.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
config-dist.php
course/classes/management_renderer.php
course/user.php
grade/export/lib.php
grade/export/ods/grade_export_ods.php
grade/export/txt/grade_export_txt.php
grade/export/txt/tests/behat/export.feature
grade/export/xls/grade_export_xls.php
grade/report/upgrade.txt
grade/report/user/lib.php
grade/tests/behat/grade_view.feature
group/lib.php
lib/behat/classes/behat_selectors.php
lib/classes/session/memcache.php
lib/classes/session/memcached.php
lib/classes/task/send_new_user_passwords_task.php
lib/ddl/tests/ddl_test.php
lib/editor/atto/autosave-ajax.php [new file with mode: 0644]
lib/editor/atto/db/install.xml [new file with mode: 0755]
lib/editor/atto/db/upgrade.php
lib/editor/atto/lang/en/editor_atto.php
lib/editor/atto/lib.php
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js
lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js
lib/editor/atto/plugins/image/yui/src/button/js/button.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js
lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js
lib/editor/atto/plugins/undo/yui/src/button/js/button.js
lib/editor/atto/settings.php
lib/editor/atto/styles.css
lib/editor/atto/version.php
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/build.json
lib/editor/atto/yui/src/editor/js/autosave.js [new file with mode: 0644]
lib/editor/atto/yui/src/editor/js/editor.js
lib/editor/atto/yui/src/editor/js/notify.js [new file with mode: 0644]
lib/editor/atto/yui/src/editor/meta/editor.json
lib/grouplib.php
lib/javascript.php
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputcomponents.php
lib/statslib.php
lib/tests/behat/behat_general.php
lib/tests/html_writer_test.php
lib/tests/modinfolib_test.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
mod/choice/lib.php
mod/choice/view.php
mod/forum/db/install.xml
mod/forum/db/upgrade.php
mod/forum/version.php
mod/lti/OAuth.php
mod/lti/classes/event/unknown_service_api_called.php
mod/lti/locallib.php
mod/lti/service.php
mod/lti/tests/event/unknown_service_api_called_test.php [new file with mode: 0644]
mod/lti/tests/locallib_test.php
mod/quiz/module.js
mod/scorm/aicc.php
mod/scorm/classes/report.php [moved from mod/scorm/report/default.php with 63% similarity]
mod/scorm/datamodel.php
mod/scorm/datamodels/aicc.js
mod/scorm/datamodels/aicclib.php
mod/scorm/datamodels/debug.js.php
mod/scorm/datamodels/scorm_12.js
mod/scorm/datamodels/scorm_12.php
mod/scorm/datamodels/scorm_12lib.php
mod/scorm/datamodels/scorm_13.js
mod/scorm/datamodels/scorm_13.php
mod/scorm/datamodels/scorm_13lib.php
mod/scorm/datamodels/scormlib.php
mod/scorm/datamodels/sequencinghandler.php
mod/scorm/datamodels/sequencinglib.php
mod/scorm/db/log.php
mod/scorm/db/renamedclasses.php [new file with mode: 0644]
mod/scorm/db/subplugins.php
mod/scorm/grade.php
mod/scorm/index.php
mod/scorm/lib.php
mod/scorm/loadSCO.php
mod/scorm/locallib.php
mod/scorm/module.js
mod/scorm/player.js
mod/scorm/player.php
mod/scorm/prereqs.php
mod/scorm/report.php
mod/scorm/report/basic/classes/report.php [moved from mod/scorm/report/basic/report.php with 82% similarity]
mod/scorm/report/basic/db/renamedclasses.php [new file with mode: 0644]
mod/scorm/report/graphs/classes/report.php [moved from mod/scorm/report/graphs/report.php with 75% similarity]
mod/scorm/report/graphs/db/renamedclasses.php [new file with mode: 0644]
mod/scorm/report/graphs/version.php
mod/scorm/report/interactions/classes/report.php [moved from mod/scorm/report/interactions/report.php with 83% similarity]
mod/scorm/report/interactions/db/renamedclasses.php [new file with mode: 0644]
mod/scorm/report/interactions/responsessettings_form.php
mod/scorm/report/interactions/version.php
mod/scorm/report/objectives/classes/report.php [moved from mod/scorm/report/objectives/report.php with 84% similarity]
mod/scorm/report/objectives/db/renamedclasses.php [new file with mode: 0644]
mod/scorm/report/reportlib.php
mod/scorm/report/userreport.php
mod/scorm/report/userreportinteractions.php
mod/scorm/settings.php
mod/scorm/tabs.php
mod/scorm/tests/formatduration_test.php
mod/scorm/tests/generator_test.php
mod/scorm/upgrade.txt [new file with mode: 0644]
mod/scorm/view.js
mod/scorm/view.php
question/behaviour/manualgraded/tests/walkthrough_test.php
question/format.php
question/type/edit_question_form.php
question/type/essay/renderer.php
question/type/essay/tests/walkthrough_test.php
theme/base/style/core.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
theme/javascript.php

index ed5fb41..e1a0fb0 100644 (file)
@@ -142,14 +142,14 @@ function print_auth_lock_options($auth, $user_fields, $helptext, $retrieveopts,
         $fieldname = $field;
         if ($fieldname === 'lang') {
             $fieldname = get_string('language');
-        } elseif (preg_match('/^(.+?)(\d+)$/', $fieldname, $matches)) {
-            $fieldname =  get_string($matches[1]) . ' ' . $matches[2];
-        } elseif ($fieldname == 'url') {
-            $fieldname = get_string('webpage');
         } elseif (!empty($customfields) && in_array($field, $customfields)) {
             // If custom field then pick name from database.
             $fieldshortname = str_replace('profile_field_', '', $fieldname);
             $fieldname = $customfieldname[$fieldshortname]->name;
+        } elseif (preg_match('/^(.+?)(\d+)$/', $fieldname, $matches)) {
+            $fieldname =  get_string($matches[1]) . ' ' . $matches[2];
+        } elseif ($fieldname == 'url') {
+            $fieldname = get_string('webpage');
         } else {
             $fieldname = get_string($fieldname);
         }
index 6efafce..63b6ce4 100644 (file)
@@ -63,7 +63,7 @@ if (!defined('E_USER_DEPRECATED')) {
 /**
  * phpCAS version. accessible for the user by phpCAS::getVersion().
  */
-define('PHPCAS_VERSION', '1.3.2');
+define('PHPCAS_VERSION', '1.3.3');
 
 /**
  * @addtogroup public
@@ -78,6 +78,10 @@ define("CAS_VERSION_1_0", '1.0');
  * CAS version 2.0
 */
 define("CAS_VERSION_2_0", '2.0');
+/**
+ * CAS version 3.0
+ */
+define("CAS_VERSION_3_0", '3.0');
 
 // ------------------------------------------------------------------------
 //  SAML defines
@@ -318,18 +322,6 @@ class phpCAS
         if (is_object(self::$_PHPCAS_CLIENT)) {
             phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_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 initializer is called from
         $dbg = debug_backtrace();
@@ -341,10 +333,14 @@ class phpCAS
         );
 
         // initialize the object $_PHPCAS_CLIENT
-        self::$_PHPCAS_CLIENT = new CAS_Client(
-            $server_version, false, $server_hostname, $server_port, $server_uri,
-            $changeSessionID
-        );
+        try {
+            self::$_PHPCAS_CLIENT = new CAS_Client(
+                $server_version, false, $server_hostname, $server_port, $server_uri,
+                $changeSessionID
+            );
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
+        }
         phpCAS :: traceEnd();
     }
 
@@ -370,18 +366,6 @@ class phpCAS
         if (is_object(self::$_PHPCAS_CLIENT)) {
             phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_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 = debug_backtrace();
@@ -393,10 +377,14 @@ class phpCAS
         );
 
         // initialize the object $_PHPCAS_CLIENT
-        self::$_PHPCAS_CLIENT = new CAS_Client(
-            $server_version, true, $server_hostname, $server_port, $server_uri,
-            $changeSessionID
-        );
+        try {
+            self::$_PHPCAS_CLIENT = new CAS_Client(
+                $server_version, true, $server_hostname, $server_port, $server_uri,
+                $changeSessionID
+            );
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
+        }
         phpCAS :: traceEnd();
     }
 
@@ -636,13 +624,13 @@ class phpCAS
      */
     public static function setLang($lang)
     {
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setLang($lang);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setLang($lang);
     }
 
     /** @} */
@@ -682,13 +670,13 @@ class phpCAS
      */
     public static function setHTMLHeader($header)
     {
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setHTMLHeader($header);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setHTMLHeader($header);
     }
 
     /**
@@ -700,13 +688,13 @@ class phpCAS
      */
     public static function setHTMLFooter($footer)
     {
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setHTMLFooter($footer);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setHTMLFooter($footer);
     }
 
     /** @} */
@@ -729,19 +717,13 @@ class phpCAS
     public static function setPGTStorage($storage)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')');
-        }
-        if ( !($storage instanceof CAS_PGTStorage) ) {
-            phpCAS :: error('type mismatched for parameter $storage (should be a CAS_PGTStorage `object\')');
+        phpCAS::_validateProxyExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setPGTStorage($storage);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setPGTStorage($storage);
         phpCAS :: traceEnd();
     }
 
@@ -766,25 +748,13 @@ class phpCAS
         $password='', $table='', $driver_options=null
     ) {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')');
-        }
-        if (gettype($username) != 'string') {
-            phpCAS :: error('type mismatched for parameter $username (should be `string\')');
-        }
-        if (gettype($password) != 'string') {
-            phpCAS :: error('type mismatched for parameter $password (should be `string\')');
-        }
-        if (gettype($table) != 'string') {
-            phpCAS :: error('type mismatched for parameter $table (should be `string\')');
+        phpCAS::_validateProxyExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options);
         phpCAS :: traceEnd();
     }
 
@@ -799,19 +769,13 @@ class phpCAS
     public static function setPGTStorageFile($path = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called before ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() (called at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ')');
-        }
-        if (gettype($path) != 'string') {
-            phpCAS :: error('type mismatched for parameter $path (should be `string\')');
+        phpCAS::_validateProxyExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setPGTStorageFile($path);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setPGTStorageFile($path);
         phpCAS :: traceEnd();
     }
     /** @} */
@@ -836,23 +800,13 @@ class phpCAS
     public static function getProxiedService ($type)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            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 (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
-        }
-        if (gettype($type) != 'string') {
-            phpCAS :: error('type mismatched for parameter $type (should be `string\')');
-        }
+        phpCAS::_validateProxyExists();
 
-        $res = self::$_PHPCAS_CLIENT->getProxiedService($type);
+        try {
+            $res = self::$_PHPCAS_CLIENT->getProxiedService($type);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
+        }
 
         phpCAS :: traceEnd();
         return $res;
@@ -872,20 +826,13 @@ class phpCAS
      */
     public static function initializeProxiedService (CAS_ProxiedService $proxiedService)
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            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 (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
-        }
+        phpCAS::_validateProxyExists();
 
-        self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService);
+        try {
+            self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
+        }
     }
 
     /**
@@ -906,23 +853,13 @@ class phpCAS
     public static function serviceWeb($url, & $err_code, & $output)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            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 (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
-        }
-        if (gettype($url) != 'string') {
-            phpCAS :: error('type mismatched for parameter $url (should be `string\')');
-        }
+        phpCAS::_validateProxyExists();
 
-        $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output);
+        try {
+            $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
+        }
 
         phpCAS :: traceEnd($res);
         return $res;
@@ -950,28 +887,14 @@ class phpCAS
     public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->isProxy()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            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 (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
-        }
-        if (gettype($url) != 'string') {
-            phpCAS :: error('type mismatched for parameter $url (should be `string\')');
-        }
+        phpCAS::_validateProxyExists();
 
-        if (gettype($flags) != 'integer') {
-            phpCAS :: error('type mismatched for parameter $flags (should be `integer\')');
+        try {
+            $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
 
-        $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt);
-
         phpCAS :: traceEnd($res);
         return $res;
     }
@@ -998,13 +921,13 @@ class phpCAS
      */
     public static function setCacheTimesForAuthRecheck($n)
     {
-        if (!is_object(self::$_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 $n (should be `integer\')');
+        phpCAS::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n);
     }
 
     /**
@@ -1028,9 +951,7 @@ class phpCAS
      */
     public static function setPostAuthenticateCallback ($function, array $additionalArgs = array())
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
 
         self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs);
     }
@@ -1051,9 +972,7 @@ class phpCAS
      */
     public static function setSingleSignoutCallback ($function, array $additionalArgs = array())
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
 
         self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs);
     }
@@ -1071,9 +990,7 @@ class phpCAS
     public static function checkAuthentication()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
 
         $auth = self::$_PHPCAS_CLIENT->checkAuthentication();
 
@@ -1094,16 +1011,13 @@ class phpCAS
     public static function forceAuthentication()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-
+        phpCAS::_validateClientExists();
         $auth = self::$_PHPCAS_CLIENT->forceAuthentication();
 
         // store where the authentication has been checked and the result
         self::$_PHPCAS_CLIENT->markAuthenticationCall($auth);
 
-        /*             if (!$auth) {
+        /*      if (!$auth) {
          phpCAS :: trace('user is not authenticated, redirecting to the CAS server');
         self::$_PHPCAS_CLIENT->forceAuthentication();
         } else {
@@ -1122,9 +1036,8 @@ class phpCAS
     public static function renewAuthentication()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         $auth = self::$_PHPCAS_CLIENT->renewAuthentication();
 
         // store where the authentication has been checked and the result
@@ -1143,9 +1056,7 @@ class phpCAS
     public static function isAuthenticated()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
 
         // call the isAuthenticated method of the $_PHPCAS_CLIENT object
         $auth = self::$_PHPCAS_CLIENT->isAuthenticated();
@@ -1166,9 +1077,8 @@ class phpCAS
      */
     public static function isSessionAuthenticated()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         return (self::$_PHPCAS_CLIENT->isSessionAuthenticated());
     }
 
@@ -1176,65 +1086,56 @@ class phpCAS
      * This method returns the CAS user's login name.
      *
      * @return string the login name of the authenticated user
-     * @warning should not be called only after phpCAS::forceAuthentication()
+     * @warning should only be called after phpCAS::forceAuthentication()
      * or phpCAS::checkAuthentication().
      * */
     public static function getUser()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
+        phpCAS::_validateClientExists();
+
+        try {
+            return self::$_PHPCAS_CLIENT->getUser();
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return self::$_PHPCAS_CLIENT->getUser();
     }
 
     /**
      * Answer attributes about the authenticated user.
      *
-     * @warning should not be called only after phpCAS::forceAuthentication()
+     * @warning should only be called after phpCAS::forceAuthentication()
      * or phpCAS::checkAuthentication().
      *
      * @return array
      */
     public static function getAttributes()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
+        phpCAS::_validateClientExists();
+
+        try {
+            return self::$_PHPCAS_CLIENT->getAttributes();
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return self::$_PHPCAS_CLIENT->getAttributes();
     }
 
     /**
      * Answer true if there are attributes for the authenticated user.
      *
-     * @warning should not be called only after phpCAS::forceAuthentication()
+     * @warning should only be called after phpCAS::forceAuthentication()
      * or phpCAS::checkAuthentication().
      *
      * @return bool
      */
     public static function hasAttributes()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
+        phpCAS::_validateClientExists();
+
+        try {
+            return self::$_PHPCAS_CLIENT->hasAttributes();
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return self::$_PHPCAS_CLIENT->hasAttributes();
     }
 
     /**
@@ -1243,21 +1144,18 @@ class phpCAS
      * @param string $key attribute name
      *
      * @return bool
-     * @warning should not be called only after phpCAS::forceAuthentication()
+     * @warning should only be called after phpCAS::forceAuthentication()
      * or phpCAS::checkAuthentication().
      */
     public static function hasAttribute($key)
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
+        phpCAS::_validateClientExists();
+
+        try {
+            return self::$_PHPCAS_CLIENT->hasAttribute($key);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return self::$_PHPCAS_CLIENT->hasAttribute($key);
     }
 
     /**
@@ -1266,21 +1164,18 @@ class phpCAS
      * @param string $key attribute name
      *
      * @return mixed string for a single value or an array if multiple values exist.
-     * @warning should not be called only after phpCAS::forceAuthentication()
+     * @warning should only be called after phpCAS::forceAuthentication()
      * or phpCAS::checkAuthentication().
      */
     public static function getAttribute($key)
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCalled()) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::forceAuthentication() or ' . __CLASS__ . '::isAuthenticated()');
-        }
-        if (!self::$_PHPCAS_CLIENT->wasAuthenticationCallSuccessful()) {
-            phpCAS :: error('authentication was checked (by ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerMethod() . '() at ' . self::$_PHPCAS_CLIENT->getAuthenticationCallerFile() . ':' . self::$_PHPCAS_CLIENT->getAuthenticationCallerLine() . ') but the method returned false');
+        phpCAS::_validateClientExists();
+
+        try {
+            return self::$_PHPCAS_CLIENT->getAttribute($key);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return self::$_PHPCAS_CLIENT->getAttribute($key);
     }
 
     /**
@@ -1293,9 +1188,8 @@ class phpCAS
      */
     public static function handleLogoutRequests($check_client = true, $allowed_clients = false)
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients));
     }
 
@@ -1307,9 +1201,8 @@ class phpCAS
      */
     public static function getServerLoginURL()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         return self::$_PHPCAS_CLIENT->getServerLoginURL();
     }
 
@@ -1324,13 +1217,14 @@ class phpCAS
     public static function setServerLoginURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setServerLoginURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setServerLoginURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1345,13 +1239,14 @@ class phpCAS
     public static function setServerServiceValidateURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1366,13 +1261,14 @@ class phpCAS
     public static function setServerProxyValidateURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1386,13 +1282,14 @@ class phpCAS
     public static function setServerSamlValidateURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1404,9 +1301,8 @@ class phpCAS
      */
     public static function getServerLogoutURL()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should not be called before ' . __CLASS__ . '::client() or ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         return self::$_PHPCAS_CLIENT->getServerLogoutURL();
     }
 
@@ -1421,17 +1317,14 @@ class phpCAS
     public static function setServerLogoutURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setServerLogoutURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setServerLogoutURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1446,9 +1339,8 @@ class phpCAS
     public static function logout($params = "")
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         $parsedParams = array ();
         if ($params != "") {
             if (is_string($params)) {
@@ -1480,9 +1372,8 @@ class phpCAS
     public static function logoutWithRedirectService($service)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         if (!is_string($service)) {
             phpCAS :: error('type mismatched for parameter $service (should be `string\')');
         }
@@ -1532,9 +1423,8 @@ class phpCAS
     {
         trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED);
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         if (!is_string($service)) {
             phpCAS :: error('type mismatched for parameter $service (should be `string\')');
         }
@@ -1563,16 +1453,14 @@ class phpCAS
     public static function setFixedCallbackURL($url = '')
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
-        if (!self::$_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::_validateProxyExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setCallbackURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setCallbackURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1587,13 +1475,14 @@ class phpCAS
     public static function setFixedServiceURL($url)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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::_validateProxyExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setURL($url);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setURL($url);
+
         phpCAS :: traceEnd();
     }
 
@@ -1604,9 +1493,7 @@ class phpCAS
      */
     public static function getServiceURL()
     {
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateProxyExists();
         return (self::$_PHPCAS_CLIENT->getURL());
     }
 
@@ -1621,13 +1508,13 @@ class phpCAS
      */
     public static function retrievePT($target_service, & $err_code, & $err_msg)
     {
-        if (!is_object(self::$_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\')');
+        phpCAS::_validateProxyExists();
+
+        try {
+            return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg));
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg));
     }
 
     /**
@@ -1642,16 +1529,14 @@ class phpCAS
     public static function setCasServerCACert($cert, $validate_cn = true)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_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\')');
-        }
-        if (gettype($validate_cn) != 'boolean') {\r
-            phpCAS :: error('type mismatched for parameter $validate_cn (should be `boolean\')');\r
+        phpCAS::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn);
+
         phpCAS :: traceEnd();
     }
 
@@ -1663,9 +1548,8 @@ class phpCAS
     public static function setNoCasServerValidation()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.');
         self::$_PHPCAS_CLIENT->setNoCasServerValidation();
         phpCAS :: traceEnd();
@@ -1684,9 +1568,8 @@ class phpCAS
     public static function setNoClearTicketsFromUrl()
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl();
         phpCAS :: traceEnd();
     }
@@ -1705,9 +1588,8 @@ class phpCAS
     public static function setExtraCurlOption($key, $value)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
+        phpCAS::_validateClientExists();
+
         self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value);
         phpCAS :: traceEnd();
     }
@@ -1751,11 +1633,11 @@ class phpCAS
     public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
-        if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0) {
-            phpCAS :: error('this method can only be used with the cas 2.0 protool');
+        phpCAS::_validateClientExists();
+
+        if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0
+            && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0) {
+            phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols');
         }
         self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain);
         phpCAS :: traceEnd();
@@ -1772,9 +1654,7 @@ class phpCAS
      */
     public static function getProxies ()
     {
-        if ( !is_object(self::$_PHPCAS_CLIENT) ) {
-            phpCAS::error('this method should only be called after '.__CLASS__.'::client()');
-        }
+        phpCAS::_validateProxyExists();
 
         return(self::$_PHPCAS_CLIENT->getProxies());
     }
@@ -1795,13 +1675,14 @@ class phpCAS
     {
         phpCAS::traceBegin();
         phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl);
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
-        }
-        if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) {
-            phpCAS::error('type mismatched for parameter $rebroadcastNodeUrl (should be `url\')');
+        phpCAS::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl);
+
         phpCAS::traceEnd();
     }
 
@@ -1816,14 +1697,45 @@ class phpCAS
     public static function addRebroadcastHeader($header)
     {
         phpCAS :: traceBegin();
-        if (!is_object(self::$_PHPCAS_CLIENT)) {
-            phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()');
+        phpCAS::_validateClientExists();
+
+        try {
+            self::$_PHPCAS_CLIENT->addRebroadcastHeader($header);
+        } catch (Exception $e) {
+            phpCAS :: error(get_class($e) . ': ' . $e->getMessage());
         }
-        self::$_PHPCAS_CLIENT->addRebroadcastHeader($header);
+
         phpCAS :: traceEnd();
     }
-}
 
+    /**
+     * Checks if a client already exists
+     *
+     * @throws CAS_OutOfSequenceBeforeClientException
+     *
+     * @return void
+     */
+    private static function _validateClientExists()
+    {
+        if (!is_object(self::$_PHPCAS_CLIENT)) {
+            throw new CAS_OutOfSequenceBeforeClientException();
+        }
+    }
+
+    /**
+     * Checks of a proxy client aready exists
+     *
+     * @throws CAS_OutOfSequenceBeforeProxyException
+     *
+     * @return void
+     */
+    private static function _validateProxyExists()
+    {
+        if (!is_object(self::$_PHPCAS_CLIENT)) {
+            throw new CAS_OutOfSequenceBeforeProxyException();
+        }
+    }
+}
 // ########################################################################
 // DOCUMENTATION
 // ########################################################################
index 801156e..a14154d 100644 (file)
@@ -74,7 +74,7 @@ implements CAS_Exception
         printf(
             $lang->getYouWereNotAuthenticated(),
             htmlentities($client->getURL()),
-            $_SERVER['SERVER_ADMIN']
+            isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
         );
         phpCAS::trace('CAS URL: '.$cas_url);
         phpCAS::trace('Authentication failure: '.$failure);
@@ -89,6 +89,7 @@ implements CAS_Exception
                     phpCAS::trace('Reason: CAS error');
                     break;
                 case CAS_VERSION_2_0:
+                case CAS_VERSION_3_0:
                     if ( empty($err_code) ) {
                         phpCAS::trace('Reason: no CAS error');
                     } else {
index c7d436e..e56dbdf 100644 (file)
@@ -25,35 +25,39 @@ function CAS_autoload($class)
 {
     // Static to hold the Include Path to CAS
     static $include_path;
-    // Setup the include path if it's not already set from a previous call
-    if (!$include_path) {
-        $include_path = dirname(dirname(__FILE__));
-    }
+    // Check only for CAS classes
     if (substr($class, 0, 4) !== 'CAS_') {
         return false;
     }
+    // Setup the include path if it's not already set from a previous call
+    if (empty($include_path)) {
+        $include_path = array(dirname(dirname(__FILE__)), dirname(dirname(__FILE__)) . '/../test/' );
+    }
+
     // Declare local variable to store the expected full path to the file
-    $file_path = $include_path . '/' . str_replace('_', '/', $class) . '.php';
 
-    $fp = @fopen($file_path, 'r', true);
-    if ($fp) {
-        fclose($fp);
-        include $file_path;
-        if (!class_exists($class, false) && !interface_exists($class, false)) {
-            die(
-                new Exception(
-                    'Class ' . $class . ' was not present in ' .
-                    $file_path .
-                    ' [CAS_autoload]'
-                )
-            );
+    foreach ($include_path as $path) {
+        $file_path = $path . '/' . str_replace('_', '/', $class) . '.php';
+        $fp = @fopen($file_path, 'r', true);
+        if ($fp) {
+            fclose($fp);
+            include $file_path;
+            if (!class_exists($class, false) && !interface_exists($class, false)) {
+                die(
+                    new Exception(
+                        'Class ' . $class . ' was not present in ' .
+                        $file_path .
+                        ' [CAS_autoload]'
+                    )
+                );
+            }
+            return true;
         }
-        return true;
     }
     $e = new Exception(
         'Class ' . $class . ' could not be loaded from ' .
         $file_path . ', file does not exist (Path="'
-        . $include_path .'") [CAS_autoload]'
+        . implode(':', $include_path) .'") [CAS_autoload]'
     );
     $trace = $e->getTrace();
     if (isset($trace[2]) && isset($trace[2]['function'])
@@ -71,9 +75,13 @@ function CAS_autoload($class)
 
 // set up __autoload
 if (function_exists('spl_autoload_register')) {
-    if (!(spl_autoload_functions()) || !in_array('CAS_autoload', spl_autoload_functions())) {
+    if (!(spl_autoload_functions())
+        || !in_array('CAS_autoload', spl_autoload_functions())
+    ) {
         spl_autoload_register('CAS_autoload');
-        if (function_exists('__autoload') && !in_array('__autoload', spl_autoload_functions())) {
+        if (function_exists('__autoload')
+            && !in_array('__autoload', spl_autoload_functions())
+        ) {
             // __autoload() was being used, but now would be ignored, add
             // it to the autoload stack
             spl_autoload_register('__autoload');
index f5d0d4e..40e77cd 100644 (file)
@@ -147,6 +147,10 @@ class CAS_Client
      */
     public function setHTMLHeader($header)
     {
+       // Argument Validation
+       if (gettype($header) != 'string')
+               throw new CAS_TypeMismatchException($header, '$header', 'string');
+
         $this->_output_header = $header;
     }
 
@@ -159,6 +163,10 @@ class CAS_Client
      */
     public function setHTMLFooter($footer)
     {
+       // Argument Validation
+       if (gettype($footer) != 'string')
+               throw new CAS_TypeMismatchException($footer, '$footer', 'string');
+
         $this->_output_footer = $footer;
     }
 
@@ -190,10 +198,16 @@ class CAS_Client
      */
     public function setLang($lang)
     {
+       // Argument Validation
+       if (gettype($lang) != 'string')
+               throw new CAS_TypeMismatchException($lang, '$lang', 'string');
+
         phpCAS::traceBegin();
         $obj = new $lang();
         if (!($obj instanceof CAS_Languages_LanguageInterface)) {
-            throw new CAS_InvalidArgumentException('$className must implement the CAS_Languages_LanguageInterface');
+            throw new CAS_InvalidArgumentException(
+                '$className must implement the CAS_Languages_LanguageInterface'
+            );
         }
         $this->_lang = $lang;
         phpCAS::traceEnd();
@@ -350,6 +364,10 @@ class CAS_Client
      */
     public function setServerLoginURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_server['login_url'] = $url;
     }
 
@@ -363,6 +381,10 @@ class CAS_Client
      */
     public function setServerServiceValidateURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_server['service_validate_url'] = $url;
     }
 
@@ -376,6 +398,10 @@ class CAS_Client
      */
     public function setServerProxyValidateURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_server['proxy_validate_url'] = $url;
     }
 
@@ -389,6 +415,10 @@ class CAS_Client
      */
     public function setServerSamlValidateURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_server['saml_validate_url'] = $url;
     }
 
@@ -412,9 +442,16 @@ class CAS_Client
                 $this->_server['service_validate_url'] = $this->_getServerBaseURL()
                 .'serviceValidate';
                 break;
+            case CAS_VERSION_3_0:
+                $this->_server['service_validate_url'] = $this->_getServerBaseURL()
+                .'p3/serviceValidate';
+                break;
             }
         }
-        $url = $this->_buildQueryUrl($this->_server['service_validate_url'], 'service='.urlencode($this->getURL()));
+        $url = $this->_buildQueryUrl(
+            $this->_server['service_validate_url'],
+            'service='.urlencode($this->getURL())
+        );
         phpCAS::traceEnd($url);
         return $url;
     }
@@ -435,7 +472,10 @@ class CAS_Client
             }
         }
 
-        $url = $this->_buildQueryUrl($this->_server['saml_validate_url'], 'TARGET='.urlencode($this->getURL()));
+        $url = $this->_buildQueryUrl(
+            $this->_server['saml_validate_url'],
+            'TARGET='.urlencode($this->getURL())
+        );
         phpCAS::traceEnd($url);
         return $url;
     }
@@ -457,9 +497,15 @@ class CAS_Client
             case CAS_VERSION_2_0:
                 $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
                 break;
+            case CAS_VERSION_3_0:
+                $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate';
+                break;
             }
         }
-        $url = $this->_buildQueryUrl($this->_server['proxy_validate_url'], 'service='.urlencode($this->getURL()));
+        $url = $this->_buildQueryUrl(
+            $this->_server['proxy_validate_url'],
+            'service='.urlencode($this->getURL())
+        );
         phpCAS::traceEnd($url);
         return $url;
     }
@@ -479,6 +525,7 @@ class CAS_Client
                 $this->_server['proxy_url'] = '';
                 break;
             case CAS_VERSION_2_0:
+            case CAS_VERSION_3_0:
                 $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
                 break;
             }
@@ -509,6 +556,10 @@ class CAS_Client
      */
     public function setServerLogoutURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_server['logout_url'] = $url;
     }
 
@@ -561,7 +612,9 @@ class CAS_Client
     {
         $obj = new $className;
         if (!($obj instanceof CAS_Request_RequestInterface)) {
-            throw new CAS_InvalidArgumentException('$className must implement the CAS_Request_RequestInterface');
+            throw new CAS_InvalidArgumentException(
+                '$className must implement the CAS_Request_RequestInterface'
+            );
         }
         $this->_requestImplementation = $className;
     }
@@ -656,6 +709,20 @@ class CAS_Client
     //  Methods for supplying code-flow feedback to integrators.
     // ########################################################################
 
+    /**
+     * Ensure that this is actually a proxy object or fail with an exception
+     *
+     * @throws CAS_OutOfSequenceProxyException
+     *
+     * @return void
+     */
+    public function ensureIsProxy()
+    {
+        if (!$this->isProxy()) {
+            throw new CAS_OutOfSequenceProxyException();
+        }
+    }
+
     /**
      * Mark the caller of authentication. This will help client integraters determine
      * problems with their code flow if they call a function such as getUser() before
@@ -688,6 +755,21 @@ class CAS_Client
         return !empty($this->_authentication_caller);
     }
 
+    /**
+     * Ensure that authentication was checked. Terminate with exception if no
+     * authentication was performed
+     *
+     * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
+     *
+     * @return void
+     */
+    private function _ensureAuthenticationCalled()
+    {
+        if (!$this->wasAuthenticationCalled()) {
+            throw new CAS_OutOfSequenceBeforeAuthenticationCallException();
+        }
+    }
+
     /**
      * Answer the result of the authentication call.
      *
@@ -698,12 +780,33 @@ class CAS_Client
      */
     public function wasAuthenticationCallSuccessful ()
     {
-        if (empty($this->_authentication_caller)) {
-            throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
-        }
+        $this->_ensureAuthenticationCalled();
         return $this->_authentication_caller['result'];
     }
 
+
+    /**
+     * Ensure that authentication was checked. Terminate with exception if no
+     * authentication was performed
+     *
+     * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
+     *
+     * @return void
+     */
+    public function ensureAuthenticationCallSuccessful()
+    {
+        $this->_ensureAuthenticationCalled();
+        if (!$this->_authentication_caller['result']) {
+            throw new CAS_OutOfSequenceException(
+                'authentication was checked (by '
+                . $this->getAuthenticationCallerMethod()
+                . '() at ' . $this->getAuthenticationCallerFile()
+                . ':' . $this->getAuthenticationCallerLine()
+                . ') but the method returned false'
+            );
+        }
+    }
+
     /**
      * Answer information about the authentication caller.
      *
@@ -714,9 +817,7 @@ class CAS_Client
      */
     public function getAuthenticationCallerFile ()
     {
-        if (empty($this->_authentication_caller)) {
-            throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
-        }
+        $this->_ensureAuthenticationCalled();
         return $this->_authentication_caller['file'];
     }
 
@@ -730,9 +831,7 @@ class CAS_Client
      */
     public function getAuthenticationCallerLine ()
     {
-        if (empty($this->_authentication_caller)) {
-            throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
-        }
+        $this->_ensureAuthenticationCalled();
         return $this->_authentication_caller['line'];
     }
 
@@ -746,9 +845,7 @@ class CAS_Client
      */
     public function getAuthenticationCallerMethod ()
     {
-        if (empty($this->_authentication_caller)) {
-            throw new CAS_OutOfSequenceException('markAuthenticationCall() hasn\'t happened.');
-        }
+        $this->_ensureAuthenticationCalled();
         return $this->_authentication_caller['method'];
     }
 
@@ -770,7 +867,9 @@ class CAS_Client
      * @param string $server_hostname the hostname of the CAS server
      * @param int    $server_port     the port the CAS server is running on
      * @param string $server_uri      the URI the CAS server is responding on
-     * @param bool   $changeSessionID Allow phpCAS to change the session_id (Single Sign Out/handleLogoutRequests is based on that change)
+     * @param bool   $changeSessionID Allow phpCAS to change the session_id
+     *                                (Single Sign Out/handleLogoutRequests
+     *                                is based on that change)
      *
      * @return a newly created CAS_Client object
      */
@@ -782,15 +881,29 @@ class CAS_Client
         $server_uri,
         $changeSessionID = true
     ) {
+               // Argument validation
+        if (gettype($server_version) != 'string')
+               throw new CAS_TypeMismatchException($server_version, '$server_version', 'string');
+        if (gettype($proxy) != 'boolean')
+               throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean');
+        if (gettype($server_hostname) != 'string')
+               throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string');
+        if (gettype($server_port) != 'integer')
+               throw new CAS_raTypeMismatchException($server_port, '$server_port', 'integer');
+        if (gettype($server_uri) != 'string')
+               throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string');
+        if (gettype($changeSessionID) != 'boolean')
+               throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean');
 
         phpCAS::traceBegin();
-
-        $this->_setChangeSessionID($changeSessionID); // true : allow to change the session_id(), false session_id won't be change and logout won't be handle because of that
+        // true : allow to change the session_id(), false session_id won't be
+        // change and logout won't be handle because of that
+        $this->_setChangeSessionID($changeSessionID);
 
         // skip Session Handling for logout requests and if don't want it'
         if (session_id()=="" && !$this->_isLogoutRequest()) {
-            phpCAS :: trace("Starting a new session");
             session_start();
+            phpCAS :: trace("Starting a new session " . session_id());
         }
 
         // are we in proxy mode ?
@@ -804,7 +917,9 @@ class CAS_Client
             if (!isset($_SESSION['phpCAS']['service_cookies'])) {
                 $_SESSION['phpCAS']['service_cookies'] = array();
             }
-            $this->_serviceCookieJar = new CAS_CookieJar($_SESSION['phpCAS']['service_cookies']);
+            $this->_serviceCookieJar = new CAS_CookieJar(
+                $_SESSION['phpCAS']['service_cookies']
+            );
         }
 
         //check version
@@ -817,6 +932,7 @@ class CAS_Client
             }
             break;
         case CAS_VERSION_2_0:
+        case CAS_VERSION_3_0:
             break;
         case SAML_VERSION_1_1:
             break;
@@ -860,7 +976,9 @@ class CAS_Client
         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');
+                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
@@ -872,7 +990,10 @@ class CAS_Client
                 unset($_GET['ticket']);
             } else if ( !empty($ticket) ) {
                 //ill-formed ticket, halt
-                phpCAS::error('ill-formed ticket found in the URL (ticket=`'.htmlentities($ticket).'\')');
+                phpCAS::error(
+                    'ill-formed ticket found in the URL (ticket=`'
+                    .htmlentities($ticket).'\')'
+                );
             }
 
         }
@@ -964,6 +1085,23 @@ class CAS_Client
      */
     public function getUser()
     {
+       // Sequence validation
+       $this->ensureAuthenticationCallSuccessful();
+
+       return $this->_getUser();
+    }
+
+    /**
+     * This method returns the CAS user's login name.
+     *
+     * @return string the login name of the authenticated user
+     *
+     * @warning should be called only after CAS_Client::forceAuthentication() or
+     * CAS_Client::isAuthenticated(), otherwise halt with an error.
+     */
+    private function _getUser()
+    {
+       // This is likely a duplicate check that could be removed....
         if ( empty($this->_user) ) {
             phpCAS::error(
                 'this method should be used only after '.__CLASS__
@@ -1001,6 +1139,9 @@ class CAS_Client
      */
     public function getAttributes()
     {
+       // Sequence validation
+       $this->ensureAuthenticationCallSuccessful();
+       // This is likely a duplicate check that could be removed....
         if ( empty($this->_user) ) {
             // if no user is set, there shouldn't be any attributes also...
             phpCAS::error(
@@ -1018,6 +1159,9 @@ class CAS_Client
      */
     public function hasAttributes()
     {
+       // Sequence validation
+       $this->ensureAuthenticationCallSuccessful();
+
         return !empty($this->_attributes);
     }
     /**
@@ -1028,6 +1172,21 @@ class CAS_Client
      * @return bool is attribute available
      */
     public function hasAttribute($key)
+    {
+       // Sequence validation
+       $this->ensureAuthenticationCallSuccessful();
+
+        return $this->_hasAttribute($key);
+    }
+
+    /**
+     * Check whether a specific attribute with a name is available
+     *
+     * @param string $key name of attribute
+     *
+     * @return bool is attribute available
+     */
+    private function _hasAttribute($key)
     {
         return (is_array($this->_attributes)
             && array_key_exists($key, $this->_attributes));
@@ -1042,7 +1201,10 @@ class CAS_Client
      */
     public function getAttribute($key)
     {
-        if ($this->hasAttribute($key)) {
+       // Sequence validation
+       $this->ensureAuthenticationCallSuccessful();
+
+        if ($this->_hasAttribute($key)) {
             return $this->_attributes[$key];
         }
     }
@@ -1114,6 +1276,9 @@ class CAS_Client
      */
     public function setCacheTimesForAuthRecheck($n)
     {
+       if (gettype($n) != 'integer')
+               throw new CAS_TypeMismatchException($n, '$n', 'string');
+
         $this->_cache_times_for_auth_recheck = $n;
     }
 
@@ -1159,7 +1324,9 @@ class CAS_Client
                         .$this->_cache_times_for_auth_recheck.')'
                     );
                 } else {
-                    phpCAS::trace('user is not authenticated (cached for until login pressed)');
+                    phpCAS::trace(
+                        'user is not authenticated (cached for until login pressed)'
+                    );
                 }
             } else {
                 $_SESSION['phpCAS']['unauth_count'] = 0;
@@ -1189,21 +1356,28 @@ class CAS_Client
         if ( $this->_wasPreviouslyAuthenticated() ) {
             if ($this->hasTicket()) {
                 // User has a additional ticket but was already authenticated
-                phpCAS::trace('ticket was present and will be discarded, use renewAuthenticate()');
+                phpCAS::trace(
+                    'ticket was present and will be discarded, use renewAuthenticate()'
+                );
                 if ($this->_clearTicketsFromUrl) {
                     phpCAS::trace("Prepare redirect to : ".$this->getURL());
+                    session_write_close();
                     header('Location: '.$this->getURL());
                     flush();
                     phpCAS::traceExit();
                     throw new CAS_GracefullTerminationException();
                 } else {
-                    phpCAS::trace('Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.');
+                    phpCAS::trace(
+                        'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'
+                    );
                     $res = true;
                 }
             } else {
                 // 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');
+                phpCAS::trace(
+                    'user was already authenticated, no need to look for tickets'
+                );
                 $res = true;
             }
         } else {
@@ -1211,26 +1385,41 @@ class CAS_Client
                 switch ($this->getServerVersion()) {
                 case CAS_VERSION_1_0:
                     // if a Service Ticket was given, validate it
-                    phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' is present');
-                    $this->validateCAS10($validate_url, $text_response, $tree_response); // if it fails, it halts
-                    phpCAS::trace('CAS 1.0 ticket `'.$this->getTicket().'\' was validated');
-                    $_SESSION['phpCAS']['user'] = $this->getUser();
+                    phpCAS::trace(
+                        'CAS 1.0 ticket `'.$this->getTicket().'\' is present'
+                    );
+                    $this->validateCAS10(
+                        $validate_url, $text_response, $tree_response
+                    ); // if it fails, it halts
+                    phpCAS::trace(
+                        'CAS 1.0 ticket `'.$this->getTicket().'\' was validated'
+                    );
+                    $_SESSION['phpCAS']['user'] = $this->_getUser();
                     $res = true;
                     $logoutTicket = $this->getTicket();
                     break;
                 case CAS_VERSION_2_0:
+                case CAS_VERSION_3_0:
                     // if a Proxy Ticket was given, validate it
-                    phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' is present');
-                    $this->validateCAS20($validate_url, $text_response, $tree_response); // note: if it fails, it halts
-                    phpCAS::trace('CAS 2.0 ticket `'.$this->getTicket().'\' was validated');
+                    phpCAS::trace(
+                        'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present'
+                    );
+                    $this->validateCAS20(
+                        $validate_url, $text_response, $tree_response
+                    ); // note: if it fails, it halts
+                    phpCAS::trace(
+                        'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated'
+                    );
                     if ( $this->isProxy() ) {
-                        $this->_validatePGT($validate_url, $text_response, $tree_response); // idem
+                        $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();
-                    if ($this->hasAttributes()) {
-                        $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
+                    $_SESSION['phpCAS']['user'] = $this->_getUser();
+                    if (!empty($this->_attributes)) {
+                        $_SESSION['phpCAS']['attributes'] = $this->_attributes;
                     }
                     $proxies = $this->getProxies();
                     if (!empty($proxies)) {
@@ -1241,11 +1430,17 @@ class CAS_Client
                     break;
                 case SAML_VERSION_1_1:
                     // if we have a SAML ticket, validate it.
-                    phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' is present');
-                    $this->validateSA($validate_url, $text_response, $tree_response); // if it fails, it halts
-                    phpCAS::trace('SAML 1.1 ticket `'.$this->getTicket().'\' was validated');
-                    $_SESSION['phpCAS']['user'] = $this->getUser();
-                    $_SESSION['phpCAS']['attributes'] = $this->getAttributes();
+                    phpCAS::trace(
+                        'SAML 1.1 ticket `'.$this->getTicket().'\' is present'
+                    );
+                    $this->validateSA(
+                        $validate_url, $text_response, $tree_response
+                    ); // if it fails, it halts
+                    phpCAS::trace(
+                        'SAML 1.1 ticket `'.$this->getTicket().'\' was validated'
+                    );
+                    $_SESSION['phpCAS']['user'] = $this->_getUser();
+                    $_SESSION['phpCAS']['attributes'] = $this->_attributes;
                     $res = true;
                     $logoutTicket = $this->getTicket();
                     break;
@@ -1258,15 +1453,13 @@ class CAS_Client
                 phpCAS::trace('no ticket found');
             }
             if ($res) {
-                // Mark the auth-check as complete to allow post-authentication
-                // callbacks to make use of phpCAS::getUser() and similar methods
-                $this->markAuthenticationCall($res);
-
                 // call the post-authenticate callback if registered.
                 if ($this->_postAuthenticateCallbackFunction) {
                     $args = $this->_postAuthenticateCallbackArgs;
                     array_unshift($args, $logoutTicket);
-                    call_user_func_array($this->_postAuthenticateCallbackFunction, $args);
+                    call_user_func_array(
+                        $this->_postAuthenticateCallbackFunction, $args
+                    );
                 }
 
                 // if called with a ticket parameter, we need to redirect to the
@@ -1277,6 +1470,7 @@ class CAS_Client
                 // security precaution to prevent a ticket in the HTTP_REFERRER
                 if ($this->_clearTicketsFromUrl) {
                     phpCAS::trace("Prepare redirect to : ".$this->getURL());
+                    session_write_close();
                     header('Location: '.$this->getURL());
                     flush();
                     phpCAS::traceExit();
@@ -1284,7 +1478,9 @@ class CAS_Client
                 }
             }
         }
-
+        // Mark the auth-check as complete to allow post-authentication
+        // callbacks to make use of phpCAS::getUser() and similar methods
+        $this->markAuthenticationCall($res);
         phpCAS::traceEnd($res);
         return $res;
     }
@@ -1323,31 +1519,49 @@ class CAS_Client
 
         if ( $this->isProxy() ) {
             // CAS proxy: username and PGT must be present
-            if ( $this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
+            if ( $this->isSessionAuthenticated()
+                && !empty($_SESSION['phpCAS']['pgt'])
+            ) {
                 // authentication already done
                 $this->_setUser($_SESSION['phpCAS']['user']);
                 if (isset($_SESSION['phpCAS']['attributes'])) {
                     $this->setAttributes($_SESSION['phpCAS']['attributes']);
                 }
                 $this->_setPGT($_SESSION['phpCAS']['pgt']);
-                phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'.$_SESSION['phpCAS']['pgt'].'\'');
+                phpCAS::trace(
+                    'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `'
+                    .$_SESSION['phpCAS']['pgt'].'\''
+                );
 
                 // Include the list of proxies
                 if (isset($_SESSION['phpCAS']['proxies'])) {
                     $this->_setProxies($_SESSION['phpCAS']['proxies']);
-                    phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
+                    phpCAS::trace(
+                        'proxies = "'
+                        .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
+                    );
                 }
 
                 $auth = true;
-            } elseif ( $this->isSessionAuthenticated() && empty($_SESSION['phpCAS']['pgt']) ) {
+            } 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');
+                phpCAS::trace(
+                    'username found (`'.$_SESSION['phpCAS']['user']
+                    .'\') but PGT is empty'
+                );
                 // unset all tickets to enforce authentication
                 unset($_SESSION['phpCAS']);
                 $this->setTicket('');
-            } elseif ( !$this->isSessionAuthenticated() && !empty($_SESSION['phpCAS']['pgt']) ) {
+            } 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');
+                phpCAS::trace(
+                    'PGT found (`'.$_SESSION['phpCAS']['pgt']
+                    .'\') but username is empty'
+                );
                 // unset all tickets to enforce authentication
                 unset($_SESSION['phpCAS']);
                 $this->setTicket('');
@@ -1367,7 +1581,10 @@ class CAS_Client
                 // Include the list of proxies
                 if (isset($_SESSION['phpCAS']['proxies'])) {
                     $this->_setProxies($_SESSION['phpCAS']['proxies']);
-                    phpCAS::trace('proxies = "'.implode('", "', $_SESSION['phpCAS']['proxies']).'"');
+                    phpCAS::trace(
+                        'proxies = "'
+                        .implode('", "', $_SESSION['phpCAS']['proxies']).'"'
+                    );
                 }
 
                 $auth = true;
@@ -1394,6 +1611,7 @@ class CAS_Client
     {
         phpCAS::traceBegin();
         $cas_url = $this->getServerLoginURL($gateway, $renew);
+        session_write_close();
         if (php_sapi_name() === 'cli') {
             @header('Location: '.$cas_url);
         } else {
@@ -1423,11 +1641,13 @@ class CAS_Client
         $cas_url = $this->getServerLogoutURL();
         $paramSeparator = '?';
         if (isset($params['url'])) {
-            $cas_url = $cas_url . $paramSeparator . "url=" . urlencode($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']);
+            $cas_url = $cas_url . $paramSeparator . "service="
+                . urlencode($params['service']);
         }
         header('Location: '.$cas_url);
         phpCAS::trace("Prepare redirect to : ".$cas_url);
@@ -1470,8 +1690,12 @@ class CAS_Client
             phpCAS::traceEnd();
             return;
         }
-        if (!$this->getChangeSessionID() && is_null($this->_signoutCallbackFunction)) {
-            phpCAS::trace("phpCAS can't handle logout requests if it is not allowed to change session_id.");
+        if (!$this->getChangeSessionID()
+            && is_null($this->_signoutCallbackFunction)
+        ) {
+            phpCAS::trace(
+                "phpCAS can't handle logout requests if it is not allowed to change session_id."
+            );
         }
         phpCAS::trace("Logout requested");
         $decoded_logout_rq = urldecode($_POST['logoutRequest']);
@@ -1485,12 +1709,19 @@ class CAS_Client
             $client = gethostbyaddr($client_ip);
             phpCAS::trace("Client: ".$client."/".$client_ip);
             foreach ($allowed_clients as $allowed_client) {
-                if (($client == $allowed_client) or ($client_ip == $allowed_client)) {
-                    phpCAS::trace("Allowed client '".$allowed_client."' matches, logout request is allowed");
+                if (($client == $allowed_client)
+                    || ($client_ip == $allowed_client)
+                ) {
+                    phpCAS::trace(
+                        "Allowed client '".$allowed_client
+                        ."' matches, logout request is allowed"
+                    );
                     $allowed = true;
                     break;
                 } else {
-                    phpCAS::trace("Allowed client '".$allowed_client."' does not match");
+                    phpCAS::trace(
+                        "Allowed client '".$allowed_client."' does not match"
+                    );
                 }
             }
         } else {
@@ -1505,9 +1736,16 @@ class CAS_Client
                 $this->_rebroadcast(self::LOGOUT);
             }
             // Extract the ticket from the SAML Request
-            preg_match("|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|", $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3);
-            $wrappedSamlSessionIndex = preg_replace('|<samlp:SessionIndex>|', '', $tick[0][0]);
-            $ticket2logout = preg_replace('|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex);
+            preg_match(
+                "|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|",
+                $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3
+            );
+            $wrappedSamlSessionIndex = preg_replace(
+                '|<samlp:SessionIndex>|', '', $tick[0][0]
+            );
+            $ticket2logout = preg_replace(
+                '|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex
+            );
             phpCAS::trace("Ticket to logout: ".$ticket2logout);
 
             // call the post-authenticate callback if registered.
@@ -1517,7 +1755,8 @@ class CAS_Client
                 call_user_func_array($this->_signoutCallbackFunction, $args);
             }
 
-            // If phpCAS is managing the session_id, destroy session thanks to session_id.
+            // If phpCAS is managing the session_id, destroy session thanks to
+            // session_id.
             if ($this->getChangeSessionID()) {
                 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket2logout);
                 phpCAS::trace("Session id: ".$session_id);
@@ -1623,11 +1862,16 @@ class CAS_Client
     private $_cas_server_ca_cert = null;
 
 
-    /**\r
-     * validate CN of the CAS server certificate\r
-     *\r
-     * @hideinitializer\r
-     */\r
+    /**
+
+     * validate CN of the CAS server certificate
+
+     *
+
+     * @hideinitializer
+
+     */
+
     private $_cas_server_cn_validate = true;
 
     /**
@@ -1649,6 +1893,12 @@ class CAS_Client
      */
     public function setCasServerCACert($cert, $validate_cn)
     {
+       // Argument validation
+       if (gettype($cert) != 'string')
+               throw new CAS_TypeMismatchException($cert, '$cert', 'string');
+        if (gettype($validate_cn) != 'boolean')
+               throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean');
+
         $this->_cas_server_ca_cert = $cert;
         $this->_cas_server_cn_validate = $validate_cn;
     }
@@ -1682,11 +1932,14 @@ class CAS_Client
         phpCAS::traceBegin();
         $result = false;
         // build the URL to validate the ticket
-        $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket();
+        $validate_url = $this->getServerServiceValidateURL()
+            .'&ticket='.urlencode($this->getTicket());
 
         // open and read the URL
         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
-            phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
+            phpCAS::trace(
+                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
+            );
             throw new CAS_AuthenticationException(
                 $this, 'CAS 1.0 ticket not validated', $validate_url,
                 true/*$no_response*/
@@ -1757,8 +2010,12 @@ class CAS_Client
 
         // open and read the URL
         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
-            phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
-            throw new CAS_AuthenticationException($this, 'SA not validated', $validate_url, true/*$no_response*/);
+            phpCAS::trace(
+                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
+            );
+            throw new CAS_AuthenticationException(
+                $this, 'SA not validated', $validate_url, true/*$no_response*/
+            );
         }
 
         phpCAS::trace('server version: '.$this->getServerVersion());
@@ -1791,7 +2048,10 @@ class CAS_Client
                 $result = false;
             } else if ( $tree_response->localName != 'Envelope' ) {
                 // insure that tag name is 'Envelope'
-                phpCAS::trace('bad XML root node (should be `Envelope\' instead of `'.$tree_response->localName.'\'');
+                phpCAS::trace(
+                    'bad XML root node (should be `Envelope\' instead of `'
+                    .$tree_response->localName.'\''
+                );
                 throw new CAS_AuthenticationException(
                     $this, 'SA not validated', $validate_url,
                     false/*$no_response*/, true/*$bad_response*/,
@@ -1865,7 +2125,7 @@ class CAS_Client
                 foreach ($attr_array as $attr_key => $attr_value) {
                     if (count($attr_value) > 1) {
                         $this->_attributes[$attr_key] = $attr_value;
-                        phpCAS::trace("* " . $attr_key . "=" . $attr_value);
+                        phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true));
                     } else {
                         $this->_attributes[$attr_key] = $attr_value[0];
                         phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
@@ -2037,11 +2297,11 @@ class CAS_Client
             $final_uri = '';
             // remove the ticket if present in the URL
             $final_uri = 'https://';
-            $final_uri .= $this->_getServerUrl();
+            $final_uri .= $this->_getClientUrl();
             $request_uri = $_SERVER['REQUEST_URI'];
             $request_uri = preg_replace('/\?.*$/', '', $request_uri);
             $final_uri .= $request_uri;
-            $this->setCallbackURL($final_uri);
+            $this->_callback_url = $final_uri;
         }
         return $this->_callback_url;
     }
@@ -2055,6 +2315,12 @@ class CAS_Client
      */
     public function setCallbackURL($url)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         return $this->_callback_url = $url;
     }
 
@@ -2172,15 +2438,17 @@ class CAS_Client
      */
     public function setPGTStorage($storage)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+
         // check that the storage has not already been set
         if ( is_object($this->_pgt_storage) ) {
             phpCAS::error('PGT storage already defined');
         }
 
         // check to make sure a valid storage object was specified
-        if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) {
-            phpCAS::error('Invalid PGT storage object');
-        }
+        if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) )
+            throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object');
 
         // store the PGTStorage object
         $this->_pgt_storage = $storage;
@@ -2203,10 +2471,28 @@ class CAS_Client
      *
      * @return void
      */
-    public function setPGTStorageDb($dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
-    {
+    public function setPGTStorageDb(
+        $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null
+    ) {
+       // Sequence validation
+        $this->ensureIsProxy();
+
+       // Argument validation
+       if ((is_object($dsn_or_pdo) && !($dsn_or_pdo instanceof PDO)) || gettype($dsn_or_pdo) != 'string')
+                       throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object');
+       if (gettype($username) != 'string')
+               throw new CAS_TypeMismatchException($username, '$username', 'string');
+        if (gettype($password) != 'string')
+               throw new CAS_TypeMismatchException($password, '$password', 'string');
+        if (gettype($table) != 'string')
+               throw new CAS_TypeMismatchException($table, '$password', 'string');
+
         // create the storage object
-        $this->setPGTStorage(new CAS_PGTStorage_Db($this, $dsn_or_pdo, $username, $password, $table, $driver_options));
+        $this->setPGTStorage(
+            new CAS_PGTStorage_Db(
+                $this, $dsn_or_pdo, $username, $password, $table, $driver_options
+            )
+        );
     }
 
     /**
@@ -2219,6 +2505,13 @@ class CAS_Client
      */
     public function setPGTStorageFile($path='')
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+
+       // Argument validation
+       if (gettype($path) != 'string')
+               throw new CAS_TypeMismatchException($path, '$path', 'string');
+
         // create the storage object
         $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
     }
@@ -2232,7 +2525,9 @@ class CAS_Client
     *
     * @param string &$validate_url the URL of the request to the CAS server.
     * @param string $text_response the response of the CAS server, as is
-    * (XML text); result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
+    *                              (XML text); result of
+    *                              CAS_Client::validateCAS10() or
+    *                              CAS_Client::validateCAS20().
     * @param string $tree_response the response of the CAS server, as a DOM XML
     * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
     *
@@ -2252,13 +2547,16 @@ class CAS_Client
             );
         } else {
             // PGT Iou transmitted, extract it
-            $pgt_iou = trim($tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue);
+            $pgt_iou = trim(
+                $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue
+            );
             if (preg_match('/PGTIOU-[\.\-\w]/', $pgt_iou)) {
                 $pgt = $this->_loadPGT($pgt_iou);
                 if ( $pgt == false ) {
                     phpCAS::trace('could not load PGT');
                     throw new CAS_AuthenticationException(
-                        $this, 'PGT Iou was transmitted but PGT could not be retrieved',
+                        $this,
+                        'PGT Iou was transmitted but PGT could not be retrieved',
                         $validate_url, false/*$no_response*/,
                         false/*$bad_response*/, $text_response
                     );
@@ -2292,6 +2590,10 @@ class CAS_Client
      */
     public function retrievePT($target_service,&$err_code,&$err_msg)
     {
+       // Argument validation
+       if (gettype($target_service) != 'string')
+               throw new CAS_TypeMismatchException($target_service, '$target_service', 'string');
+
         phpCAS::traceBegin();
 
         // by default, $err_msg is set empty and $pt to true. On error, $pt is
@@ -2301,11 +2603,14 @@ class CAS_Client
         $err_msg = '';
 
         // build the URL to retrieve the PT
-        $cas_url = $this->getServerProxyURL().'?targetService='.urlencode($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, $headers, $cas_response, $err_msg) ) {
-            phpCAS::trace('could not open URL \''.$cas_url.'\' to validate ('.$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);
@@ -2354,7 +2659,9 @@ class CAS_Client
                 if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
                     $err_code = PHPCAS_SERVICE_OK;
                     $err_msg = '';
-                    $pt = trim($proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue);
+                    $pt = trim(
+                        $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue
+                    );
                     phpCAS::trace('original PT: '.trim($pt));
                     phpCAS::traceEnd($pt);
                     return $pt;
@@ -2382,7 +2689,8 @@ class CAS_Client
         // at this step, we are sure that the response of the CAS server was
         // illformed
         $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
-        $err_msg = 'Invalid response from the CAS server (response=`'.$cas_response.'\')';
+        $err_msg = 'Invalid response from the CAS server (response=`'
+            .$cas_response.'\')';
 
         phpCAS::traceEnd(false);
         return false;
@@ -2425,10 +2733,14 @@ class CAS_Client
         $request->setUrl($url);
 
         if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
-            phpCAS::error('one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.');
+            phpCAS::error(
+                'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'
+            );
         }
         if ($this->_cas_server_ca_cert != '') {
-            $request->setSslCaCert($this->_cas_server_ca_cert, $this->_cas_server_cn_validate);
+            $request->setSslCaCert(
+                $this->_cas_server_ca_cert, $this->_cas_server_cn_validate
+            );
         }
 
         // add extra stuff if SAML
@@ -2468,9 +2780,11 @@ class CAS_Client
         phpCAS::traceBegin();
 
         //get the ticket
-        $sa = $this->getTicket();
+        $sa = urlencode($this->getTicket());
 
-        $body=SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST.SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE.SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
+        $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST
+            .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE
+            .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
 
         phpCAS::traceEnd($body);
         return ($body);
@@ -2500,6 +2814,14 @@ class CAS_Client
      */
     public function getProxiedService ($type)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+       $this->ensureAuthenticationCallSuccessful();
+
+       // Argument validation
+       if (gettype($type) != 'string')
+               throw new CAS_TypeMismatchException($type, '$type', 'string');
+
         switch ($type) {
         case PHPCAS_PROXIED_SERVICE_HTTP_GET:
         case PHPCAS_PROXIED_SERVICE_HTTP_POST:
@@ -2514,13 +2836,15 @@ class CAS_Client
             }
             return $proxiedService;
         case PHPCAS_PROXIED_SERVICE_IMAP;
-            $proxiedService = new CAS_ProxiedService_Imap($this->getUser());
+            $proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
             if ($proxiedService instanceof CAS_ProxiedService_Testable) {
                 $proxiedService->setCasClient($this);
             }
             return $proxiedService;
         default:
-            throw new CAS_InvalidArgumentException("Unknown proxied-service type, $type.");
+            throw new CAS_InvalidArgumentException(
+                "Unknown proxied-service type, $type."
+            );
         }
     }
 
@@ -2541,9 +2865,17 @@ class CAS_Client
      */
     public function initializeProxiedService (CAS_ProxiedService $proxiedService)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+       $this->ensureAuthenticationCallSuccessful();
+
         $url = $proxiedService->getServiceUrl();
         if (!is_string($url)) {
-            throw new CAS_ProxiedService_Exception("Proxied Service ".get_class($proxiedService)."->getServiceUrl() should have returned a string, returned a ".gettype($url)." instead.");
+            throw new CAS_ProxiedService_Exception(
+                "Proxied Service ".get_class($proxiedService)
+                ."->getServiceUrl() should have returned a string, returned a "
+                .gettype($url)." instead."
+            );
         }
         $pt = $this->retrievePT($url, $err_code, $err_msg);
         if (!$pt) {
@@ -2568,6 +2900,14 @@ class CAS_Client
      */
     public function serviceWeb($url,&$err_code,&$output)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+       $this->ensureAuthenticationCallSuccessful();
+
+       // Argument validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         try {
             $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
             $service->setUrl($url);
@@ -2581,7 +2921,9 @@ class CAS_Client
             return false;
         } catch (CAS_ProxiedService_Exception $e) {
             $lang = $this->getLangObj();
-            $output = sprintf($lang->getServiceUnavailable(), $url, $e->getMessage());
+            $output = sprintf(
+                $lang->getServiceUnavailable(), $url, $e->getMessage()
+            );
             $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
             return false;
         }
@@ -2608,6 +2950,18 @@ class CAS_Client
      */
     public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
     {
+       // Sequence validation
+        $this->ensureIsProxy();
+       $this->ensureAuthenticationCallSuccessful();
+
+       // Argument validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+        if (gettype($serviceUrl) != 'string')
+               throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string');
+        if (gettype($flags) != 'integer')
+               throw new CAS_TypeMismatchException($flags, '$flags', 'string');
+
         try {
             $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
             $service->setServiceUrl($serviceUrl);
@@ -2748,9 +3102,11 @@ class CAS_Client
         $result = false;
         // build the URL to validate the ticket
         if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
-            $validate_url = $this->getServerProxyValidateURL().'&ticket='.$this->getTicket();
+            $validate_url = $this->getServerProxyValidateURL().'&ticket='
+                .urlencode($this->getTicket());
         } else {
-            $validate_url = $this->getServerServiceValidateURL().'&ticket='.$this->getTicket();
+            $validate_url = $this->getServerServiceValidateURL().'&ticket='
+                .urlencode($this->getTicket());
         }
 
         if ( $this->isProxy() ) {
@@ -2760,7 +3116,9 @@ class CAS_Client
 
         // open and read the URL
         if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
-            phpCAS::trace('could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')');
+            phpCAS::trace(
+                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
+            );
             throw new CAS_AuthenticationException(
                 $this, 'Ticket not validated', $validate_url,
                 true/*$no_response*/
@@ -2800,7 +3158,8 @@ class CAS_Client
             $result = false;
         } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
             // authentication succeded, extract the user name
-            $success_elements = $tree_response->getElementsByTagName("authenticationSuccess");
+            $success_elements = $tree_response
+                ->getElementsByTagName("authenticationSuccess");
             if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
                 // no user specified => error
                 throw new CAS_AuthenticationException(
@@ -2809,7 +3168,11 @@ class CAS_Client
                 );
                 $result = false;
             } else {
-                $this->_setUser(trim($success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue));
+                $this->_setUser(
+                    trim(
+                        $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue
+                    )
+                );
                 $this->_readExtraAttributesCas20($success_elements);
                 // Store the proxies we are sitting behind for authorization checking
                 $proxyList = array();
@@ -2835,7 +3198,8 @@ class CAS_Client
             }
         } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
             // authentication succeded, extract the error code and message
-            $auth_fail_list = $tree_response->getElementsByTagName("authenticationFailure");
+            $auth_fail_list = $tree_response
+                ->getElementsByTagName("authenticationFailure");
             throw new CAS_AuthenticationException(
                 $this, 'Ticket not validated', $validate_url,
                 false/*$no_response*/, false/*$bad_response*/,
@@ -2894,13 +3258,20 @@ class CAS_Client
         //     </cas:serviceResponse>
         //
         if ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) {
-            $attr_nodes = $success_elements->item(0)->getElementsByTagName("attributes");
+            $attr_nodes = $success_elements->item(0)
+                ->getElementsByTagName("attributes");
             phpCas :: trace("Found nested jasig style attributes");
             if ($attr_nodes->item(0)->hasChildNodes()) {
                 // Nested Attributes
                 foreach ($attr_nodes->item(0)->childNodes as $attr_child) {
-                    phpCas :: trace("Attribute [".$attr_child->localName."] = ".$attr_child->nodeValue);
-                    $this->_addAttributeToArray($extra_attributes, $attr_child->localName, $attr_child->nodeValue);
+                    phpCas :: trace(
+                        "Attribute [".$attr_child->localName."] = "
+                        .$attr_child->nodeValue
+                    );
+                    $this->_addAttributeToArray(
+                        $extra_attributes, $attr_child->localName,
+                        $attr_child->nodeValue
+                    );
                 }
             }
         } else {
@@ -2930,8 +3301,13 @@ class CAS_Client
                     continue;
                 default:
                     if (strlen(trim($attr_node->nodeValue))) {
-                        phpCas :: trace("Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue);
-                        $this->_addAttributeToArray($extra_attributes, $attr_node->localName, $attr_node->nodeValue);
+                        phpCas :: trace(
+                            "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue
+                        );
+                        $this->_addAttributeToArray(
+                            $extra_attributes, $attr_node->localName,
+                            $attr_node->nodeValue
+                        );
                     }
                 }
             }
@@ -2957,16 +3333,30 @@ class CAS_Client
         //             </cas:authenticationSuccess>
         //     </cas:serviceResponse>
         //
-        if (!count($extra_attributes) && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0) {
-            $attr_nodes = $success_elements->item(0)->getElementsByTagName("attribute");
+        if (!count($extra_attributes)
+            && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0
+        ) {
+            $attr_nodes = $success_elements->item(0)
+                ->getElementsByTagName("attribute");
             $firstAttr = $attr_nodes->item(0);
-            if (!$firstAttr->hasChildNodes() && $firstAttr->hasAttribute('name') && $firstAttr->hasAttribute('value')) {
+            if (!$firstAttr->hasChildNodes()
+                && $firstAttr->hasAttribute('name')
+                && $firstAttr->hasAttribute('value')
+            ) {
                 phpCas :: trace("Found Name-Value style attributes");
                 // Nested Attributes
                 foreach ($attr_nodes as $attr_node) {
-                    if ($attr_node->hasAttribute('name') && $attr_node->hasAttribute('value')) {
-                        phpCas :: trace("Attribute [".$attr_node->getAttribute('name')."] = ".$attr_node->getAttribute('value'));
-                        $this->_addAttributeToArray($extra_attributes, $attr_node->getAttribute('name'), $attr_node->getAttribute('value'));
+                    if ($attr_node->hasAttribute('name')
+                        && $attr_node->hasAttribute('value')
+                    ) {
+                        phpCas :: trace(
+                            "Attribute [".$attr_node->getAttribute('name')
+                            ."] = ".$attr_node->getAttribute('value')
+                        );
+                        $this->_addAttributeToArray(
+                            $extra_attributes, $attr_node->getAttribute('name'),
+                            $attr_node->getAttribute('value')
+                        );
                     }
                 }
             }
@@ -3036,6 +3426,10 @@ class CAS_Client
      */
     public function setURL($url)
     {
+       // Argument Validation
+       if (gettype($url) != 'string')
+               throw new CAS_TypeMismatchException($url, '$url', 'string');
+
         $this->_url = $url;
     }
 
@@ -3055,14 +3449,15 @@ class CAS_Client
             $final_uri = ($this->_isHttps()) ? 'https' : 'http';
             $final_uri .= '://';
 
-            $final_uri .= $this->_getServerUrl();
+            $final_uri .= $this->_getClientUrl();
             $request_uri       = explode('?', $_SERVER['REQUEST_URI'], 2);
             $final_uri         .= $request_uri[0];
 
             if (isset($request_uri[1]) && $request_uri[1]) {
                 $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
 
-                // If the query string still has anything left, append it to the final URI
+                // If the query string still has anything left,
+                // append it to the final URI
                 if ($query_string !== '') {
                     $final_uri .= "?$query_string";
                 }
@@ -3077,11 +3472,11 @@ class CAS_Client
 
 
     /**
-     * Try to figure out the server URL with possible Proxys / Ports etc.
+     * Try to figure out the phpCas client URL with possible Proxys / Ports etc.
      *
      * @return string Server URL with domain:port
      */
-    private function _getServerUrl()
+    private function _getClientUrl()
     {
         $server_url = '';
         if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) {
@@ -3101,7 +3496,8 @@ class CAS_Client
             if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) {
                 $server_port = $_SERVER['SERVER_PORT'];
             } else {
-                $server_port = $_SERVER['HTTP_X_FORWARDED_PORT'];
+                $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']);
+                $server_port = $ports[0];
             }
 
             if ( ($this->_isHttps() && $server_port!=443)
@@ -3121,7 +3517,13 @@ class CAS_Client
      */
     private function _isHttps()
     {
-        if ( isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+        if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
+            return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
+        }
+        if ( isset($_SERVER['HTTPS'])
+            && !empty($_SERVER['HTTPS'])
+            && $_SERVER['HTTPS'] != 'off'
+        ) {
             return true;
         } else {
             return false;
@@ -3141,7 +3543,10 @@ class CAS_Client
     private function _removeParameterFromQueryString($parameterName, $queryString)
     {
         $parameterName = preg_quote($parameterName);
-        return preg_replace("/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", '', $queryString);
+        return preg_replace(
+            "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/",
+            '', $queryString
+        );
     }
 
     /**
@@ -3174,19 +3579,24 @@ class CAS_Client
         if ($this->getChangeSessionID()) {
             if (!empty($this->_user)) {
                 $old_session = $_SESSION;
+                phpCAS :: trace("Killing session: ". session_id());
                 session_destroy();
                 // set up a new session, of name based on the ticket
                 $session_id = preg_replace('/[^a-zA-Z0-9\-]/', '', $ticket);
-                phpCAS :: trace("Session ID: ".$session_id);
+                phpCAS :: trace("Starting session: ". $session_id);
                 session_id($session_id);
                 session_start();
                 phpCAS :: trace("Restoring old session vars");
                 $_SESSION = $old_session;
             } else {
-                phpCAS :: error('Session should only be renamed after successfull authentication');
+                phpCAS :: error(
+                    'Session should only be renamed after successfull authentication'
+                );
             }
         } else {
-            phpCAS :: trace("Skipping session rename since phpCAS is not handling the session.");
+            phpCAS :: trace(
+                "Skipping session rename since phpCAS is not handling the session."
+            );
         }
         phpCAS::traceEnd();
     }
@@ -3223,7 +3633,10 @@ class CAS_Client
         phpCAS::traceBegin();
         $lang = $this->getLangObj();
         $this->printHTMLHeader($lang->getAuthenticationFailed());
-        printf($lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), $_SERVER['SERVER_ADMIN']);
+        printf(
+            $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()),
+            $_SERVER['SERVER_ADMIN']
+        );
         phpCAS::trace('CAS URL: '.$cas_url);
         phpCAS::trace('Authentication failure: '.$failure);
         if ( $no_response ) {
@@ -3237,10 +3650,13 @@ class CAS_Client
                     phpCAS::trace('Reason: CAS error');
                     break;
                 case CAS_VERSION_2_0:
+                case CAS_VERSION_3_0:
                     if ( empty($err_code) ) {
                         phpCAS::trace('Reason: no CAS error');
                     } else {
-                        phpCAS::trace('Reason: ['.$err_code.'] CAS error: '.$err_msg);
+                        phpCAS::trace(
+                            'Reason: ['.$err_code.'] CAS error: '.$err_msg
+                        );
                     }
                     break;
                 }
@@ -3298,6 +3714,10 @@ class CAS_Client
      */
     public function addRebroadcastNode($rebroadcastNodeUrl)
     {
+       // Argument validation
+       if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl))
+               throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url');
+
         // Store the rebroadcast node and set flag
         $this->_rebroadcast = true;
         $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
@@ -3318,6 +3738,9 @@ class CAS_Client
      */
     public function addRebroadcastHeader($header)
     {
+       if (gettype($header) != 'string')
+               throw new CAS_TypeMismatchException($header, '$header', 'string');
+
         $this->_rebroadcast_headers[] = $header;
     }
 
@@ -3360,8 +3783,13 @@ class CAS_Client
         $multiRequest = new $multiClassName();
 
         for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
-            if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))) {
-                phpCAS::trace('Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']);
+            if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false))
+                || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))
+            ) {
+                phpCAS::trace(
+                    'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i]
+                    .$_SERVER['REQUEST_URI']
+                );
                 $className = $this->_requestImplementation;
                 $request = new $className();
 
@@ -3375,7 +3803,9 @@ class CAS_Client
                 $request->makePost();
                 if ($type == self::LOGOUT) {
                     // Logout request
-                    $request->setPostBody('rebroadcast=false&logoutRequest='.$_POST['logoutRequest']);
+                    $request->setPostBody(
+                        'rebroadcast=false&logoutRequest='.$_POST['logoutRequest']
+                    );
                 } else if ($type == self::PGTIOU) {
                     // pgtIou/pgtId rebroadcast
                     $request->setPostBody('rebroadcast=false');
@@ -3385,7 +3815,11 @@ class CAS_Client
 
                 $multiRequest->addRequest($request);
             } else {
-                phpCAS::trace('Rebroadcast not sent to self: '.$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'').'/'.(!empty($dns)?$dns:''));
+                phpCAS::trace(
+                    'Rebroadcast not sent to self: '
+                    .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'')
+                    .'/'.(!empty($dns)?$dns:'')
+                );
             }
         }
         // We need at least 1 request
index 7f1f62f..549b892 100644 (file)
@@ -161,7 +161,9 @@ class CAS_CookieJar
     protected function parseCookieHeader ($line, $defaultDomain)
     {
         if (!$defaultDomain) {
-            throw new CAS_InvalidArgumentException('$defaultDomain was not provided.');
+            throw new CAS_InvalidArgumentException(
+                '$defaultDomain was not provided.'
+            );
         }
 
         // Set our default values
@@ -315,10 +317,14 @@ class CAS_CookieJar
     protected function cookieMatchesTarget ($cookie, $target)
     {
         if (!is_array($target)) {
-            throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
+            throw new CAS_InvalidArgumentException(
+                '$target must be an array of URL attributes as generated by parse_url().'
+            );
         }
         if (!isset($target['host'])) {
-            throw new CAS_InvalidArgumentException('$target must be an array of URL attributes as generated by parse_url().');
+            throw new CAS_InvalidArgumentException(
+                '$target must be an array of URL attributes as generated by parse_url().'
+            );
         }
 
         // Verify that the scheme matches
@@ -352,15 +358,17 @@ class CAS_CookieJar
                 }
             }
         } else {
-            // If the cookie host doesn't begin with '.', the host must case-insensitive
-            // match exactly
+            // If the cookie host doesn't begin with '.',
+            // the host must case-insensitive match exactly
             if (strcasecmp($target['host'], $cookie['domain']) !== 0) {
                 return false;
             }
         }
 
         // Verify that the port matches
-        if (isset($cookie['ports']) && !in_array($target['port'], $cookie['ports'])) {
+        if (isset($cookie['ports'])
+            && !in_array($target['port'], $cookie['ports'])
+        ) {
             return false;
         }
 
index 84199d1..ed3150a 100644 (file)
@@ -99,7 +99,7 @@ class CAS_Languages_German implements CAS_Languages_LanguageInterface
      */
     public function getYouWereNotAuthenticated()
     {
-        return '<p>Sie wurden nicht angemeldet.</p><p>Um es erneut zu versuchen klicken Sie <a href="%s">hier</a>.</p><p>Wenn das Problem bestehen bleibt, kontkatieren Sie den <a href="mailto:%s">Administrator</a> dieser Seite.</p>';
+        return '<p>Sie wurden nicht angemeldet.</p><p>Um es erneut zu versuchen klicken Sie <a href="%s">hier</a>.</p><p>Wenn das Problem bestehen bleibt, kontaktieren Sie den <a href="mailto:%s">Administrator</a> dieser Seite.</p>';
     }
 
     /**
@@ -113,4 +113,4 @@ class CAS_Languages_German implements CAS_Languages_LanguageInterface
     }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/auth/cas/CAS/CAS/OutOfSequenceBeforeAuthenticationCallException.php b/auth/cas/CAS/CAS/OutOfSequenceBeforeAuthenticationCallException.php
new file mode 100644 (file)
index 0000000..ef83097
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Licensed to Jasig under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * Jasig licenses this file to you 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.
+ *
+ *
+ * PHP Version 5
+ *
+ * @file     CAS/OutOfSequenceBeforeAuthenticationCallException.php
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+
+/**
+ * This class defines Exceptions that should be thrown when the sequence of
+ * operations is invalid. In this case it should be thrown when an
+ * authentication call has not yet happened.
+ *
+ * @class    CAS_OutOfSequenceBeforeAuthenticationCallException
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+class CAS_OutOfSequenceBeforeAuthenticationCallException
+extends CAS_OutOfSequenceException
+implements CAS_Exception
+{
+    /**
+     * Return standard error meessage
+     *
+     * @return void
+     */
+    public function __construct ()
+    {
+        parent::__construct('An authentication call hasn\'t happened yet.');
+    }
+}
diff --git a/auth/cas/CAS/CAS/OutOfSequenceBeforeClientException.php b/auth/cas/CAS/CAS/OutOfSequenceBeforeClientException.php
new file mode 100644 (file)
index 0000000..f1ea7e2
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Licensed to Jasig under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * Jasig licenses this file to you 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.
+ *
+ *
+ * PHP Version 5
+ *
+ * @file     CAS/OutOfSequenceBeforeClientException.php
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+
+/**
+ * This class defines Exceptions that should be thrown when the sequence of
+ * operations is invalid. In this case it should be thrown when the client() or
+ *  proxy() call has not yet happened and no client or proxy object exists.
+ *
+ * @class    CAS_OutOfSequenceBeforeClientException
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+class CAS_OutOfSequenceBeforeClientException
+extends CAS_OutOfSequenceException
+implements CAS_Exception
+{
+    /**
+     * Return standard error message
+     *
+     * @return void
+     */
+    public function __construct ()
+    {
+        parent::__construct(
+            'this method cannot be called before phpCAS::client() or phpCAS::proxy()'
+        );
+    }
+}
diff --git a/auth/cas/CAS/CAS/OutOfSequenceBeforeProxyException.php b/auth/cas/CAS/CAS/OutOfSequenceBeforeProxyException.php
new file mode 100644 (file)
index 0000000..8038542
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Licensed to Jasig under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * Jasig licenses this file to you 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.
+ *
+ *
+ * PHP Version 5
+ *
+ * @file     CAS/OutOfSequenceBeforeProxyException.php
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+
+/**
+ * This class defines Exceptions that should be thrown when the sequence of
+ * operations is invalid. In this case it should be thrown when the proxy() call
+ * has not yet happened and no proxy object exists.
+ *
+ * @class    CAS_OutOfSequenceBeforeProxyException
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Joachim Fritschi <jfritschi@freenet.de>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+class CAS_OutOfSequenceBeforeProxyException
+extends CAS_OutOfSequenceException
+implements CAS_Exception
+{
+
+    /**
+     * Return standard error message
+     *
+     * @return void
+     */
+    public function __construct ()
+    {
+        parent::__construct(
+            'this method cannot be called before phpCAS::proxy()'
+        );
+    }
+}
index 6c964f5..c164898 100644 (file)
@@ -68,7 +68,9 @@ abstract class CAS_PGTStorage_AbstractStorage
     {
         phpCAS::traceBegin();
         if ( !$cas_parent->isProxy() ) {
-            phpCAS::error('defining PGT storage makes no sense when not using a CAS proxy');
+            phpCAS::error(
+                'defining PGT storage makes no sense when not using a CAS proxy'
+            );
         }
         phpCAS::traceEnd();
     }
index 1f635f8..c331ca0 100644 (file)
@@ -135,8 +135,10 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
      * @param string     $driver_options any driver options to use when
      * connecting to the database
      */
-    public function __construct($cas_parent, $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null)
-    {
+    public function __construct(
+        $cas_parent, $dsn_or_pdo, $username='', $password='', $table='',
+        $driver_options=null
+    ) {
         phpCAS::traceBegin();
         // call the ancestor's constructor
         parent::__construct($cas_parent);
@@ -188,7 +190,10 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
         // create the PDO object if it doesn't exist already
         if (!($this->_pdo instanceof PDO)) {
             try {
-                $this->_pdo = new PDO($this->_dsn, $this->_username, $this->_password, $this->_driver_options);
+                $this->_pdo = new PDO(
+                    $this->_dsn, $this->_username, $this->_password,
+                    $this->_driver_options
+                );
             }
             catch(PDOException $e) {
                 phpCAS::error('Database connection error: ' . $e->getMessage());
@@ -247,23 +252,28 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
      */
     protected function createTableSql()
     {
-        return 'CREATE TABLE ' . $this->_getTable() . ' (pgt_iou VARCHAR(255) NOT NULL PRIMARY KEY, pgt VARCHAR(255) NOT NULL)';
+        return 'CREATE TABLE ' . $this->_getTable()
+            . ' (pgt_iou VARCHAR(255) NOT NULL PRIMARY KEY, pgt VARCHAR(255) NOT NULL)';
     }
 
     /**
      * This method returns the query used to store a pgt
      *
-     * @return the store PGT SQL, :pgt and :pgt_iou are the bind params contained in the query
+     * @return the store PGT SQL, :pgt and :pgt_iou are the bind params contained
+     *         in the query
      */
     protected function storePgtSql()
     {
-        return 'INSERT INTO ' . $this->_getTable() . ' (pgt_iou, pgt) VALUES (:pgt_iou, :pgt)';
+        return 'INSERT INTO ' . $this->_getTable()
+            . ' (pgt_iou, pgt) VALUES (:pgt_iou, :pgt)';
     }
 
     /**
-     * This method returns the query used to retrieve a pgt. the first column of the first row should contain the pgt
+     * This method returns the query used to retrieve a pgt. the first column
+     * of the first row should contain the pgt
      *
-     * @return the retrieve PGT SQL, :pgt_iou is the only bind param contained in the query
+     * @return the retrieve PGT SQL, :pgt_iou is the only bind param contained
+     *         in the query
      */
     protected function retrievePgtSql()
     {
@@ -273,7 +283,8 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
     /**
      * This method returns the query used to delete a pgt.
      *
-     * @return the delete PGT SQL, :pgt_iou is the only bind param contained in the query
+     * @return the delete PGT SQL, :pgt_iou is the only bind param contained in
+     *         the query
      */
     protected function deletePgtSql()
     {
index d8d5338..fade9e7 100644 (file)
@@ -55,15 +55,20 @@ implements CAS_ProxiedService, CAS_ProxiedService_Testable
      *
      * @return void
      * @throws InvalidArgumentException If the $proxyTicket is invalid.
-     * @throws CAS_OutOfSequenceException If called after a proxy ticket has already been initialized/set.
+     * @throws CAS_OutOfSequenceException If called after a proxy ticket has
+     *         already been initialized/set.
      */
     public function setProxyTicket ($proxyTicket)
     {
         if (empty($proxyTicket)) {
-            throw new CAS_InvalidArgumentException("Trying to initialize with an empty proxy ticket.");
+            throw new CAS_InvalidArgumentException(
+                'Trying to initialize with an empty proxy ticket.'
+            );
         }
         if (!empty($this->_proxyTicket)) {
-            throw new CAS_OutOfSequenceException('Already initialized, cannot change the proxy ticket.');
+            throw new CAS_OutOfSequenceException(
+                'Already initialized, cannot change the proxy ticket.'
+            );
         }
         $this->_proxyTicket = $proxyTicket;
     }
@@ -78,7 +83,9 @@ implements CAS_ProxiedService, CAS_ProxiedService_Testable
     protected function getProxyTicket ()
     {
         if (empty($this->_proxyTicket)) {
-            throw new CAS_OutOfSequenceException('No proxy ticket yet. Call $this->initializeProxyTicket() to aquire the proxy ticket.');
+            throw new CAS_OutOfSequenceException(
+                'No proxy ticket yet. Call $this->initializeProxyTicket() to aquire the proxy ticket.'
+            );
         }
 
         return $this->_proxyTicket;
@@ -105,7 +112,9 @@ implements CAS_ProxiedService, CAS_ProxiedService_Testable
     public function setCasClient (CAS_Client $casClient)
     {
         if (!empty($this->_proxyTicket)) {
-            throw new CAS_OutOfSequenceException('Already initialized, cannot change the CAS_Client.');
+            throw new CAS_OutOfSequenceException(
+                'Already initialized, cannot change the CAS_Client.'
+            );
         }
 
         $this->_casClient = $casClient;
@@ -124,7 +133,9 @@ implements CAS_ProxiedService, CAS_ProxiedService_Testable
     protected function initializeProxyTicket()
     {
         if (!empty($this->_proxyTicket)) {
-            throw new CAS_OutOfSequenceException('Already initialized, cannot initialize again.');
+            throw new CAS_OutOfSequenceException(
+                'Already initialized, cannot initialize again.'
+            );
         }
         // Allow usage of a particular CAS_Client for unit testing.
         if (empty($this->_casClient)) {
index f17304c..abeddf8 100644 (file)
@@ -38,9 +38,8 @@
  * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
  * @link     https://wiki.jasig.org/display/CASC/phpCAS
  */
-abstract class CAS_ProxiedService_Http_Abstract
-extends CAS_ProxiedService_Abstract
-implements CAS_ProxiedService_Http
+abstract class CAS_ProxiedService_Http_Abstract extends
+CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http
 {
     /**
      * The HTTP request mechanism talking to the target service.
@@ -64,8 +63,9 @@ implements CAS_ProxiedService_Http
      *
      * @return void
      */
-    public function __construct (CAS_Request_RequestInterface $requestHandler, CAS_CookieJar $cookieJar)
-    {
+    public function __construct(CAS_Request_RequestInterface $requestHandler,
+        CAS_CookieJar $cookieJar
+    ) {
         $this->requestHandler = $requestHandler;
         $this->_cookieJar = $cookieJar;
     }
@@ -82,10 +82,12 @@ implements CAS_ProxiedService_Http
      * @return string
      * @throws Exception If no service url is available.
      */
-    public function getServiceUrl ()
+    public function getServiceUrl()
     {
         if (empty($this->_url)) {
-            throw new CAS_ProxiedService_Exception('No URL set via '.get_class($this).'->setUrl($url).');
+            throw new CAS_ProxiedService_Exception(
+                'No URL set via ' . get_class($this) . '->setUrl($url).'
+            );
         }
 
         return $this->_url;
@@ -93,7 +95,7 @@ implements CAS_ProxiedService_Http
 
     /*********************************************************
      * Configure the Request
-    *********************************************************/
+     *********************************************************/
 
     /**
      * Set the URL of the Request
@@ -103,10 +105,12 @@ implements CAS_ProxiedService_Http
      * @return void
      * @throws CAS_OutOfSequenceException If called after the Request has been sent.
      */
-    public function setUrl ($url)
+    public function setUrl($url)
     {
         if ($this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot set the URL, request already sent.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set the URL, request already sent.'
+            );
         }
         if (!is_string($url)) {
             throw new CAS_InvalidArgumentException('$url must be a string.');
@@ -117,7 +121,7 @@ implements CAS_ProxiedService_Http
 
     /*********************************************************
      * 2. Send the Request
-    *********************************************************/
+     *********************************************************/
 
     /**
      * Perform the request.
@@ -132,10 +136,12 @@ implements CAS_ProxiedService_Http
      * @throws CAS_ProxiedService_Exception If there is a failure sending the
      * request to the target service.
      */
-    public function send ()
+    public function send()
     {
         if ($this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot send, request already sent.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot send, request already sent.'
+            );
         }
 
         phpCAS::traceBegin();
@@ -144,9 +150,9 @@ implements CAS_ProxiedService_Http
         $this->initializeProxyTicket();
         $url = $this->getServiceUrl();
         if (strstr($url, '?') === false) {
-            $url = $url.'?ticket='.$this->getProxyTicket();
+            $url = $url . '?ticket=' . $this->getProxyTicket();
         } else {
-            $url = $url.'&ticket='.$this->getProxyTicket();
+            $url = $url . '&ticket=' . $this->getProxyTicket();
         }
 
         try {
@@ -199,7 +205,7 @@ implements CAS_ProxiedService_Http
      * @throws CAS_ProxiedService_Exception If there is a failure sending the
      * request to the target service.
      */
-    protected function makeRequest ($url)
+    protected function makeRequest($url)
     {
         // Verify that we are not in a redirect loop
         $this->_numRequests++;
@@ -220,9 +226,10 @@ implements CAS_ProxiedService_Http
         $this->populateRequest($request);
 
         // Perform the request.
-        phpCAS::trace('Performing proxied service request to \''.$url.'\'');
+        phpCAS::trace('Performing proxied service request to \'' . $url . '\'');
         if (!$request->send()) {
-            $message = 'Could not perform proxied service request to URL`'.$url.'\'. '.$request->getErrorMessage();
+            $message = 'Could not perform proxied service request to URL`'
+            . $url . '\'. ' . $request->getErrorMessage();
             phpCAS::trace($message);
             throw new CAS_ProxiedService_Exception($message);
         }
@@ -231,8 +238,9 @@ implements CAS_ProxiedService_Http
         $this->_cookieJar->storeCookies($url, $request->getResponseHeaders());
 
         // Follow any redirects
-        if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders())) {
-            phpCAS :: trace('Found redirect:'.$redirectUrl);
+        if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders())
+        ) {
+            phpCAS::trace('Found redirect:' . $redirectUrl);
             $this->makeRequest($redirectUrl);
         } else {
 
@@ -249,7 +257,9 @@ implements CAS_ProxiedService_Http
      *
      * @return void
      */
-    abstract protected function populateRequest (CAS_Request_RequestInterface $request);
+    abstract protected function populateRequest(
+        CAS_Request_RequestInterface $request
+    );
 
     /**
      * Answer a redirect URL if a redirect header is found, otherwise null.
@@ -258,11 +268,12 @@ implements CAS_ProxiedService_Http
      *
      * @return string or null
      */
-    protected function getRedirectUrl (array $responseHeaders)
+    protected function getRedirectUrl(array $responseHeaders)
     {
         // Check for the redirect after authentication
         foreach ($responseHeaders as $header) {
-            if (preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches)) {
+            if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches)
+            ) {
                 return trim(array_pop($matches));
             }
         }
@@ -271,14 +282,14 @@ implements CAS_ProxiedService_Http
 
     /*********************************************************
      * 3. Access the response
-    *********************************************************/
+     *********************************************************/
 
     /**
      * Answer true if our request has been sent yet.
      *
      * @return bool
      */
-    protected function hasBeenSent ()
+    protected function hasBeenSent()
     {
         return ($this->_numRequests > 0);
     }
@@ -289,10 +300,12 @@ implements CAS_ProxiedService_Http
      * @return array An array of header strings.
      * @throws CAS_OutOfSequenceException If called before the Request has been sent.
      */
-    public function getResponseHeaders ()
+    public function getResponseHeaders()
     {
         if (!$this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot access response, request not sent yet.'
+            );
         }
 
         return $this->_responseHeaders;
@@ -304,10 +317,12 @@ implements CAS_ProxiedService_Http
      * @return int
      * @throws CAS_OutOfSequenceException If called before the Request has been sent.
      */
-    public function getResponseStatusCode ()
+    public function getResponseStatusCode()
     {
         if (!$this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot access response, request not sent yet.'
+            );
         }
 
         return $this->_responseStatusCode;
@@ -319,10 +334,12 @@ implements CAS_ProxiedService_Http
      * @return string
      * @throws CAS_OutOfSequenceException If called before the Request has been sent.
      */
-    public function getResponseBody ()
+    public function getResponseBody()
     {
         if (!$this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot access response, request not sent yet.'
+            );
         }
 
         return $this->_responseBody;
@@ -334,7 +351,7 @@ implements CAS_ProxiedService_Http
      *
      * @return array An array containing cookies. E.g. array('name' => 'val');
      */
-    public function getCookies ()
+    public function getCookies()
     {
         return $this->_cookieJar->getCookies($this->getServiceUrl());
     }
index 01f509f..78e35de 100644 (file)
  *
  * Usage Example:
  *
- *                     try {
- *                             $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
- *                             $service->setUrl('http://www.example.com/path/');
- *                             $service->send();
- *                             if ($service->getResponseStatusCode() == 200)
- *                                     return $service->getResponseBody();
- *                             else
- *                                     // The service responded with an error code 404, 500, etc.
- *                                     throw new Exception('The service responded with an error.');
+ *     try {
+ *             $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
+ *             $service->setUrl('http://www.example.com/path/');
+ *             $service->send();
+ *             if ($service->getResponseStatusCode() == 200)
+ *                     return $service->getResponseBody();
+ *             else
+ *                     // The service responded with an error code 404, 500, etc.
+ *                     throw new Exception('The service responded with an error.');
  *
- *                     } catch (CAS_ProxyTicketException $e) {
- *                             if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
- *                                     return "Your login has timed out. You need to log in again.";
- *                             else
- *                                     // Other proxy ticket errors are from bad request format (shouldn't happen)
- *                                     // or CAS server failure (unlikely) so lets just stop if we hit those.
- *                                     throw $e;
- *                     } catch (CAS_ProxiedService_Exception $e) {
- *                             // Something prevented the service request from being sent or received.
- *                             // We didn't even get a valid error response (404, 500, etc), so this
- *                             // might be caused by a network error or a DNS resolution failure.
- *                             // We could handle it in some way, but for now we will just stop.
- *                             throw $e;
- *                     }
+ *     } catch (CAS_ProxyTicketException $e) {
+ *         if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
+ *                     return "Your login has timed out. You need to log in again.";
+ *             else
+ *                     // Other proxy ticket errors are from bad request format
+ *          // (shouldn't happen) or CAS server failure (unlikely)
+ *          // so lets just stop if we hit those.
+ *                     throw $e;
+ *     } catch (CAS_ProxiedService_Exception $e) {
+ *             // Something prevented the service request from being sent or received.
+ *             // We didn't even get a valid error response (404, 500, etc), so this
+ *             // might be caused by a network error or a DNS resolution failure.
+ *             // We could handle it in some way, but for now we will just stop.
+ *             throw $e;
+ *     }
  *
  * @class    CAS_ProxiedService_Http_Get
  * @category Authentication
index 643eb98..7d4ecd3 100644 (file)
  *
  * Usage Example:
  *
- *                     try {
- *                             $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_POST);
- *                             $service->setUrl('http://www.example.com/path/');
- *                             $service->setContentType('text/xml');
- *                             $service->setBody(''<?xml version="1.0"?'.'><methodCall><methodName>example.search</methodName></methodCall>');
- *                             $service->send();
- *                             if ($service->getResponseStatusCode() == 200)
- *                                     return $service->getResponseBody();
- *                             else
- *                                     // The service responded with an error code 404, 500, etc.
- *                                     throw new Exception('The service responded with an error.');
+ *     try {
+ *             $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_POST);
+ *             $service->setUrl('http://www.example.com/path/');
+ *             $service->setContentType('text/xml');
+ *             $service->setBody('<?xml version="1.0"?'.'><methodCall><methodName>example.search</methodName></methodCall>');
+ *             $service->send();
+ *             if ($service->getResponseStatusCode() == 200)
+ *                     return $service->getResponseBody();
+ *             else
+ *                     // The service responded with an error code 404, 500, etc.
+ *                     throw new Exception('The service responded with an error.');
  *
- *                     } catch (CAS_ProxyTicketException $e) {
- *                             if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
- *                                     return "Your login has timed out. You need to log in again.";
- *                             else
- *                                     // Other proxy ticket errors are from bad request format (shouldn't happen)
- *                                     // or CAS server failure (unlikely) so lets just stop if we hit those.
- *                                     throw $e;
- *                     } catch (CAS_ProxiedService_Exception $e) {
- *                             // Something prevented the service request from being sent or received.
- *                             // We didn't even get a valid error response (404, 500, etc), so this
- *                             // might be caused by a network error or a DNS resolution failure.
- *                             // We could handle it in some way, but for now we will just stop.
- *                             throw $e;
- *                     }
+ *     } catch (CAS_ProxyTicketException $e) {
+ *             if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE)
+ *                     return "Your login has timed out. You need to log in again.";
+ *             else
+ *                     // Other proxy ticket errors are from bad request format
+ *          // (shouldn't happen) or CAS server failure (unlikely) so lets just
+ *          // stop if we hit those.
+ *                     throw $e;
+ *     } catch (CAS_ProxiedService_Exception $e) {
+ *             // Something prevented the service request from being sent or received.
+ *             // We didn't even get a valid error response (404, 500, etc), so this
+ *             // might be caused by a network error or a DNS resolution failure.
+ *             // We could handle it in some way, but for now we will just stop.
+ *             throw $e;
+ *     }
  *
  * @class    CAS_ProxiedService_Http_Post
  * @category Authentication
@@ -95,7 +96,9 @@ extends CAS_ProxiedService_Http_Abstract
     public function setContentType ($contentType)
     {
         if ($this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot set the content type, request already sent.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set the content type, request already sent.'
+            );
         }
 
         $this->_contentType = $contentType;
@@ -112,7 +115,9 @@ extends CAS_ProxiedService_Http_Abstract
     public function setBody ($body)
     {
         if ($this->hasBeenSent()) {
-            throw new CAS_OutOfSequenceException('Cannot set the body, request already sent.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set the body, request already sent.'
+            );
         }
 
         $this->_body = $body;
@@ -128,7 +133,10 @@ extends CAS_ProxiedService_Http_Abstract
     protected function populateRequest (CAS_Request_RequestInterface $request)
     {
         if (empty($this->_contentType) && !empty($this->_body)) {
-            throw new CAS_ProxiedService_Exception("If you pass a POST body, you must specify a content type via ".get_class($this).'->setContentType($contentType).');
+            throw new CAS_ProxiedService_Exception(
+                "If you pass a POST body, you must specify a content type via "
+                .get_class($this).'->setContentType($contentType).'
+            );
         }
 
         $request->makePost();
index d240d94..847da28 100644 (file)
@@ -79,7 +79,9 @@ extends CAS_ProxiedService_Abstract
     public function getServiceUrl ()
     {
         if (empty($this->_url)) {
-            throw new CAS_ProxiedService_Exception('No URL set via '.get_class($this).'->getServiceUrl($url).');
+            throw new CAS_ProxiedService_Exception(
+                'No URL set via '.get_class($this).'->getServiceUrl($url).'
+            );
         }
 
         return $this->_url;
@@ -100,7 +102,9 @@ extends CAS_ProxiedService_Abstract
     public function setServiceUrl ($url)
     {
         if ($this->hasBeenOpened()) {
-            throw new CAS_OutOfSequenceException('Cannot set the URL, stream already opened.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set the URL, stream already opened.'
+            );
         }
         if (!is_string($url) || !strlen($url)) {
             throw new CAS_InvalidArgumentException('Invalid url.');
@@ -127,7 +131,9 @@ extends CAS_ProxiedService_Abstract
     public function setMailbox ($mailbox)
     {
         if ($this->hasBeenOpened()) {
-            throw new CAS_OutOfSequenceException('Cannot set the mailbox, stream already opened.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set the mailbox, stream already opened.'
+            );
         }
         if (!is_string($mailbox) || !strlen($mailbox)) {
             throw new CAS_InvalidArgumentException('Invalid mailbox.');
@@ -155,7 +161,9 @@ extends CAS_ProxiedService_Abstract
     public function setOptions ($options)
     {
         if ($this->hasBeenOpened()) {
-            throw new CAS_OutOfSequenceException('Cannot set options, stream already opened.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot set options, stream already opened.'
+            );
         }
         if (!is_int($options)) {
             throw new CAS_InvalidArgumentException('Invalid options.');
@@ -178,14 +186,19 @@ extends CAS_ProxiedService_Abstract
      *                 PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
      *                 PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
      *                 PHPCAS_SERVICE_PT_FAILURE
-     * @throws CAS_ProxiedService_Exception If there is a failure sending the request to the target service.    */
+     * @throws CAS_ProxiedService_Exception If there is a failure sending the
+     *         request to the target service.
+     */
     public function open ()
     {
         if ($this->hasBeenOpened()) {
             throw new CAS_OutOfSequenceException('Stream already opened.');
         }
         if (empty($this->_mailbox)) {
-            throw new CAS_ProxiedService_Exception('You must specify a mailbox via '.get_class($this).'->setMailbox($mailbox)');
+            throw new CAS_ProxiedService_Exception(
+                'You must specify a mailbox via '.get_class($this)
+                .'->setMailbox($mailbox)'
+            );
         }
 
         phpCAS::traceBegin();
@@ -193,13 +206,16 @@ extends CAS_ProxiedService_Abstract
         // Get our proxy ticket and append it to our URL.
         $this->initializeProxyTicket();
         phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...');
-        $this->_stream = @imap_open($this->_mailbox, $this->_username, $this->getProxyTicket(), $this->_options);
+        $this->_stream = @imap_open(
+            $this->_mailbox, $this->_username, $this->getProxyTicket(),
+            $this->_options
+        );
         if ($this->_stream) {
             phpCAS::trace('ok');
         } else {
             phpCAS::trace('could not open mailbox');
             // @todo add localization integration.
-            $message = 'IMAP Error: '.$url.' '. var_export(imap_errors(), true);
+            $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true);
             phpCAS::trace($message);
             throw new CAS_ProxiedService_Exception($message);
         }
@@ -236,7 +252,9 @@ extends CAS_ProxiedService_Abstract
     public function getStream ()
     {
         if (!$this->hasBeenOpened()) {
-            throw new CAS_OutOfSequenceException('Cannot access stream, not opened yet.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot access stream, not opened yet.'
+            );
         }
         return $this->_stream;
     }
@@ -252,7 +270,9 @@ extends CAS_ProxiedService_Abstract
     public function getImapProxyTicket ()
     {
         if (!$this->hasBeenOpened()) {
-            throw new CAS_OutOfSequenceException('Cannot access errors, stream not opened yet.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot access errors, stream not opened yet.'
+            );
         }
         return $this->getProxyTicket();
     }
index a2f6fca..51f0767 100644 (file)
@@ -31,8 +31,9 @@
  * This interface defines methods that allow proxy-authenticated service handlers
  * to be tested in unit tests.
  *
- * Classes implementing this interface SHOULD store the CAS_Client passed and initialize
- * themselves with that client rather than via the static phpCAS method. For example:
+ * Classes implementing this interface SHOULD store the CAS_Client passed and
+ * initialize themselves with that client rather than via the static phpCAS
+ * method. For example:
  *
  *             / **
  *              * Fetch our proxy ticket.
@@ -65,7 +66,8 @@ interface CAS_ProxiedService_Testable
      * @param CAS_Client $casClient Cas client object
      *
      * @return void
-     * @throws CAS_OutOfSequenceException If called after a proxy ticket has already been initialized/set.
+     * @throws CAS_OutOfSequenceException If called after a proxy ticket has
+     *         already been initialized/set.
      */
     public function setCasClient (CAS_Client $casClient);
 
index 01ce73d..2594d14 100644 (file)
@@ -59,7 +59,8 @@ implements CAS_ProxyChain_Interface
      */
     public function __construct(array $chain)
     {
-        $this->chain = array_values($chain);   // Ensure that we have an indexed array
+        // Ensure that we have an indexed array
+        $this->chain = array_values($chain);
     }
 
     /**
@@ -78,17 +79,25 @@ implements CAS_ProxyChain_Interface
                 $proxy_url = $list[$i];
                 if (preg_match('/^\/.*\/[ixASUXu]*$/s', $search)) {
                     if (preg_match($search, $proxy_url)) {
-                        phpCAS::trace("Found regexp " .  $search . " matching " . $proxy_url);
+                        phpCAS::trace(
+                            "Found regexp " .  $search . " matching " . $proxy_url
+                        );
                     } else {
-                        phpCAS::trace("No regexp match " .  $search . " != " . $proxy_url);
+                        phpCAS::trace(
+                            "No regexp match " .  $search . " != " . $proxy_url
+                        );
                         $mismatch = true;
                         break;
                     }
                 } else {
                     if (strncasecmp($search, $proxy_url, strlen($search)) == 0) {
-                        phpCAS::trace("Found string " .  $search . " matching " . $proxy_url);
+                        phpCAS::trace(
+                            "Found string " .  $search . " matching " . $proxy_url
+                        );
                     } else {
-                        phpCAS::trace("No match " .  $search . " != " . $proxy_url);
+                        phpCAS::trace(
+                            "No match " .  $search . " != " . $proxy_url
+                        );
                         $mismatch = true;
                         break;
                     }
index 57df81f..7233046 100644 (file)
@@ -60,7 +60,10 @@ implements CAS_Exception
         PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE,
         );
         if (!in_array($code, $ptCodes)) {
-            trigger_error('Invalid code '.$code.' passed. Must be one of PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, or PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE.');
+            trigger_error(
+                'Invalid code '.$code
+                .' passed. Must be one of PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, or PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE.'
+            );
         }
 
         parent::__construct($message, $code);
index 0f2e467..f3dd28b 100644 (file)
@@ -68,7 +68,9 @@ implements CAS_Request_RequestInterface
     public function setUrl ($url)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->url = $url;
@@ -86,7 +88,9 @@ implements CAS_Request_RequestInterface
     public function addCookie ($name, $value)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->cookies[$name] = $value;
@@ -105,7 +109,9 @@ implements CAS_Request_RequestInterface
     public function addCookies (array $cookies)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->cookies = array_merge($this->cookies, $cookies);
@@ -122,7 +128,9 @@ implements CAS_Request_RequestInterface
     public function addHeader ($header)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->headers[] = $header;
@@ -139,7 +147,9 @@ implements CAS_Request_RequestInterface
     public function addHeaders (array $headers)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->headers = array_merge($this->headers, $headers);
@@ -154,7 +164,9 @@ implements CAS_Request_RequestInterface
     public function makePost ()
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
 
         $this->isPost = true;
@@ -171,10 +183,14 @@ implements CAS_Request_RequestInterface
     public function setPostBody ($body)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
         if (!$this->isPost) {
-            throw new CAS_OutOfSequenceException('Cannot add a POST body to a GET request, use makePost() first.');
+            throw new CAS_OutOfSequenceException(
+                'Cannot add a POST body to a GET request, use makePost() first.'
+            );
         }
 
         $this->postBody = $body;
@@ -192,7 +208,9 @@ implements CAS_Request_RequestInterface
     public function setSslCaCert ($caCertPath,$validate_cn=true)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
         $this->caCertPath = $caCertPath;
         $this->validateCN = $validate_cn;
@@ -211,10 +229,14 @@ implements CAS_Request_RequestInterface
     public function send ()
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot send again.');
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot send again.'
+            );
         }
         if (is_null($this->url) || !$this->url) {
-            throw new CAS_OutOfSequenceException('A url must be specified via setUrl() before the request can be sent.');
+            throw new CAS_OutOfSequenceException(
+                'A url must be specified via setUrl() before the request can be sent.'
+            );
         }
         $this->_sent = true;
         return $this->sendRequest();
@@ -288,7 +310,9 @@ implements CAS_Request_RequestInterface
     public function getResponseHeaders ()
     {
         if (!$this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has not been sent yet. Cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has not been sent yet. Cannot '.__METHOD__
+            );
         }
         return $this->_responseHeaders;
     }
@@ -302,11 +326,19 @@ implements CAS_Request_RequestInterface
     public function getResponseStatusCode ()
     {
         if (!$this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has not been sent yet. Cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has not been sent yet. Cannot '.__METHOD__
+            );
         }
 
-        if (!preg_match('/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/', $this->_responseHeaders[0], $matches)) {
-            throw new CAS_Request_Exception("Bad response, no status code was found in the first line.");
+        if (!preg_match(
+            '/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/',
+            $this->_responseHeaders[0], $matches
+        )
+        ) {
+            throw new CAS_Request_Exception(
+                'Bad response, no status code was found in the first line.'
+            );
         }
 
         return intval($matches[1]);
@@ -321,7 +353,9 @@ implements CAS_Request_RequestInterface
     public function getResponseBody ()
     {
         if (!$this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has not been sent yet. Cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has not been sent yet. Cannot '.__METHOD__
+            );
         }
 
         return $this->_responseBody;
@@ -336,7 +370,9 @@ implements CAS_Request_RequestInterface
     public function getErrorMessage ()
     {
         if (!$this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has not been sent yet. Cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has not been sent yet. Cannot '.__METHOD__
+            );
         }
         return $this->_errorMessage;
     }
index a046989..410aba0 100644 (file)
@@ -64,10 +64,14 @@ implements CAS_Request_MultiRequestInterface
     public function addRequest (CAS_Request_RequestInterface $request)
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
         if (!$request instanceof CAS_Request_CurlRequest) {
-            throw new CAS_InvalidArgumentException('As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.');
+            throw new CAS_InvalidArgumentException(
+                'As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.'
+            );
         }
 
         $this->_requests[] = $request;
@@ -81,7 +85,9 @@ implements CAS_Request_MultiRequestInterface
     public function getNumRequests()
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot '.__METHOD__);
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot '.__METHOD__
+            );
         }
         return count($this->_requests);
     }
@@ -100,10 +106,14 @@ implements CAS_Request_MultiRequestInterface
     public function send ()
     {
         if ($this->_sent) {
-            throw new CAS_OutOfSequenceException('Request has already been sent cannot send again.');
+            throw new CAS_OutOfSequenceException(
+                'Request has already been sent cannot send again.'
+            );
         }
         if (!count($this->_requests)) {
-            throw new CAS_OutOfSequenceException('At least one request must be added via addRequest() before the multi-request can be sent.');
+            throw new CAS_OutOfSequenceException(
+                'At least one request must be added via addRequest() before the multi-request can be sent.'
+            );
         }
 
         $this->_sent = true;
index e20914f..ea3201e 100644 (file)
@@ -75,7 +75,9 @@ implements CAS_Request_RequestInterface
         $buf = curl_exec($ch);
         if ( $buf === false ) {
             phpCAS::trace('curl_exec() failed');
-            $this->storeErrorMessage('CURL error #'.curl_errno($ch).': '.curl_error($ch));
+            $this->storeErrorMessage(
+                'CURL error #'.curl_errno($ch).': '.curl_error($ch)
+            );
             $res = false;
         } else {
             $this->storeResponseBody($buf);
@@ -120,7 +122,7 @@ implements CAS_Request_RequestInterface
             if ($this->validateCN) {
                 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
             } else {
-                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
+                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
             }
             curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
             curl_setopt($ch, CURLOPT_CAINFO, $this->caCertPath);
diff --git a/auth/cas/CAS/CAS/TypeMismatchException.php b/auth/cas/CAS/CAS/TypeMismatchException.php
new file mode 100644 (file)
index 0000000..4a13c2d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Licensed to Jasig under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * Jasig licenses this file to you 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.
+ *
+ * PHP Version 5
+ *
+ * @file     CAS/InvalidArgumentException.php
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Adam Franco <afranco@middlebury.edu>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+
+/**
+ * Exception that denotes invalid arguments were passed.
+ *
+ * @class    CAS_InvalidArgumentException
+ * @category Authentication
+ * @package  PhpCAS
+ * @author   Adam Franco <afranco@middlebury.edu>
+ * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
+ * @link     https://wiki.jasig.org/display/CASC/phpCAS
+ */
+class CAS_TypeMismatchException
+extends CAS_InvalidArgumentException
+{
+    /**
+     * Constructor, provides a nice message.
+     *
+     * @param mixed   $argument     Argument
+     * @param string  $argumentName Argument Name
+     * @param string  $type         Type
+     * @param string  $message      Error Message
+     * @param integer $code         Code
+     *
+     * @return void
+     */
+    public function __construct (
+        $argument, $argumentName, $type, $message = '', $code = 0
+    ) {
+        if (is_object($argument)) {
+            $foundType = get_class($argument).' object';
+        } else {
+            $foundType = gettype($argument);
+        }
+
+        parent::__construct(
+            'type mismatched for parameter '
+            . $argumentName . ' (should be \'' . $type .' \'), '
+            . $foundType . ' given. ' . $message, $code
+        );
+    }
+}
+?>
index f81b870..7d24ed3 100644 (file)
@@ -1,5 +1,5 @@
-Description of phpCAS 1.3.2 library import
+Description of phpCAS 1.3.3 library import
 
 * downloaded from http://downloads.jasig.org/cas-clients/php/current/
 
-iarenaza
+merrill
index 36aabce..33f0284 100644 (file)
@@ -4,7 +4,7 @@
     <location>CAS</location>
     <name>CAS</name>
     <license>BSD</license>
-    <version>1.3.2</version>
+    <version>1.3.3</version>
     <licenseversion></licenseversion>
   </library>
 </libraries>
index 63fb8a8..eb5b176 100644 (file)
@@ -159,7 +159,7 @@ class backup_xml_transformer extends xml_contenttransformer {
         // Add the module ones. Each module supporting moodle2 backups MUST have it
         $mods = core_component::get_plugin_list('mod');
         foreach ($mods as $mod => $moddir) {
-            if (plugin_supports('mod', $mod, FEATURE_BACKUP_MOODLE2)) {
+            if (plugin_supports('mod', $mod, FEATURE_BACKUP_MOODLE2) && class_exists('backup_' . $mod . '_activity_task')) {
                 $encoders['backup_' . $mod . '_activity_task'] = 'encode_content_links';
             }
         }
index aa5c15d..4cedf01 100644 (file)
@@ -435,24 +435,24 @@ class cachestore_memcache extends cache_store implements cache_is_configurable {
         }
 
         $clustered = false;
+        $setservers = array();
         if (isset($data->clustered)) {
             $clustered = true;
-        }
 
-        $lines = explode("\n", $data->setservers);
-        $setservers = array();
-        foreach ($lines as $line) {
-            // Trim surrounding colons and default whitespace.
-            $line = trim(trim($line), ":");
-            if ($line === '') {
-                continue;
-            }
-            $setserver = explode(':', $line, 3);
-            // We don't use weights, so display a debug message.
-            if (count($setserver) > 2) {
-                debugging('Memcache Set Server '.$setserver[0].' has too many parameters.');
+            $lines = explode("\n", $data->setservers);
+            foreach ($lines as $line) {
+                // Trim surrounding colons and default whitespace.
+                $line = trim(trim($line), ":");
+                if ($line === '') {
+                    continue;
+                }
+                $setserver = explode(':', $line, 3);
+                // We don't use weights, so display a debug message.
+                if (count($setserver) > 2) {
+                    debugging('Memcache Set Server '.$setserver[0].' has too many parameters.');
+                }
+                $setservers[] = $setserver;
             }
-            $setservers[] = $setserver;
         }
 
         return array(
index b379b26..f75d8c5 100644 (file)
@@ -491,24 +491,24 @@ class cachestore_memcached extends cache_store implements cache_is_configurable
         }
 
         $clustered = false;
+        $setservers = array();
         if (isset($data->clustered)) {
             $clustered = true;
-        }
 
-        $lines = explode("\n", $data->setservers);
-        $setservers = array();
-        foreach ($lines as $line) {
-            // Trim surrounding colons and default whitespace.
-            $line = trim(trim($line), ":");
-            if ($line === '') {
-                continue;
-            }
-            $setserver = explode(':', $line, 3);
-            // We don't use weights, so display a debug message.
-            if (count($setserver) > 2) {
-                debugging('Memcached Set Server '.$setserver[0].' has too many parameters.');
+            $lines = explode("\n", $data->setservers);
+            foreach ($lines as $line) {
+                // Trim surrounding colons and default whitespace.
+                $line = trim(trim($line), ":");
+                if ($line === '') {
+                    continue;
+                }
+                $setserver = explode(':', $line, 3);
+                // We don't use weights, so display a debug message.
+                if (count($setserver) > 2) {
+                    debugging('Memcached Set Server '.$setserver[0].' has too many parameters.');
+                }
+                $setservers[] = $setserver;
             }
-            $setservers[] = $setserver;
         }
 
         return array(
index 51de3b8..baf1dde 100644 (file)
@@ -279,7 +279,7 @@ $CFG->admin = 'admin';
 //      $CFG->reverseproxy = true;
 //
 // Enable when using external SSL appliance for performance reasons.
-// Please note that site may be accessible via https: or https:, but not both!
+// Please note that site may be accessible via http: or https:, but not both!
 //      $CFG->sslproxy = true;
 //
 // This setting will cause the userdate() function not to fix %d in
index 6ed18dd..40c79d5 100644 (file)
@@ -1213,21 +1213,18 @@ class core_course_management_renderer extends plugin_renderer_base {
             }
             // Show/Hide.
             if ($course->can_change_visibility()) {
-                if ($course->visible) {
                     $actions[] = $this->output->action_icon(
                         new moodle_url($baseurl, array('action' => 'hidecourse')),
                         new pix_icon('t/show', get_string('hide')),
                         null,
                         array('data-action' => 'hide', 'class' => 'action-hide')
                     );
-                } else {
                     $actions[] = $this->output->action_icon(
                         new moodle_url($baseurl, array('action' => 'showcourse')),
                         new pix_icon('t/hide', get_string('show')),
                         null,
                         array('data-action' => 'show', 'class' => 'action-show')
                     );
-                }
             }
         }
         if (empty($actions)) {
index 79607e7..8b68c77 100644 (file)
@@ -79,6 +79,9 @@ $anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $personalco
 
 $modes = array();
 
+// Used for grade reports, it represents whether we should be viewing the report as ourselves, or as the targetted user.
+$viewasuser = false;
+
 if (has_capability('moodle/grade:viewall', $coursecontext)) {
     //ok - can view all course grades
     $modes[] = 'grade';
@@ -90,10 +93,12 @@ if (has_capability('moodle/grade:viewall', $coursecontext)) {
 } else if ($course->showgrades and has_capability('moodle/grade:viewall', $personalcontext)) {
     // ok - can view grades of this user - parent most probably
     $modes[] = 'grade';
+    $viewasuser = true;
 
 } else if ($course->showgrades and $anyreport) {
     // ok - can view grades of this user - parent most probably
     $modes[] = 'grade';
+    $viewasuser = true;
 }
 
 if (empty($modes)) {
@@ -132,7 +137,7 @@ switch ($mode) {
 
         $functionname = 'grade_report_'.$CFG->grade_profilereport.'_profilereport';
         if (function_exists($functionname)) {
-            $functionname($course, $user);
+            $functionname($course, $user, $viewasuser);
         }
         break;
 
index 35ec41d..11fe75c 100644 (file)
@@ -182,6 +182,13 @@ abstract class grade_export {
             $this->previewrows = $formdata->previewrows;
         }
 
+        if (isset($formdata->display)) {
+            $this->displaytype = $formdata->display;
+        }
+
+        if (isset($formdata->updatedgradesonly)) {
+            $this->updatedgradesonly = $formdata->updatedgradesonly;
+        }
     }
 
     /**
index 1dd953f..ec81be8 100644 (file)
@@ -21,6 +21,19 @@ class grade_export_ods extends grade_export {
 
     public $plugin = 'ods';
 
+    /**
+     * Constructor should set up all the private variables ready to be pulled
+     * @param object $course
+     * @param int $groupid id of selected group, 0 means all
+     * @param stdClass $formdata The validated data from the grade export form.
+     */
+    public function __construct($course, $groupid, $formdata) {
+        parent::__construct($course, $groupid, $formdata);
+
+        // Overrides.
+        $this->usercustomfields = true;
+    }
+
     /**
      * To be implemented by child classes
      */
index 9a0b8ad..0a379c2 100644 (file)
@@ -33,6 +33,9 @@ class grade_export_txt extends grade_export {
     public function __construct($course, $groupid, $formdata) {
         parent::__construct($course, $groupid, $formdata);
         $this->separator = $formdata->separator;
+
+        // Overrides.
+        $this->usercustomfields = true;
     }
 
     public function get_export_params() {
index c47dbe9..db0ba72 100644 (file)
@@ -16,23 +16,9 @@ Feature: I need to export grades as text
       | user | course | role |
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Assignment" to section "1" and I fill the form with:
-      | Assignment name | Test assignment name |
-      | Description | Submit your online text |
-      | assignsubmission_onlinetext_enabled | 1 |
-    And I log out
-    And I log in as "student1"
-    And I follow "Course 1"
-    And I follow "Test assignment name"
-    When I press "Add submission"
-    And I set the following fields to these values:
-      | Online text | This is a submission |
-    And I press "Save changes"
-    Then I should see "Submitted for grading"
-    And I log out
+    And the following "activities" exist:
+      | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled |
+      | assign | C1 | a1 | Test assignment name | Submit your online text | 1 |
     And I log in as "teacher1"
     And I follow "Course 1"
     And I follow "Grades"
@@ -51,3 +37,13 @@ Feature: I need to export grades as text
     And I should see "80.0"
     And I should not see "Course total"
     And I should not see "80.00"
+
+  @javascript
+  Scenario: Export grades as text using percentages
+    When I set the field "Grade report" to "Plain text file"
+    And I expand all fieldsets
+    And I set the field "Grade export display type" to "Percent"
+    And I click on "Course total" "checkbox"
+    And I press "Download"
+    Then I should see "Student,1"
+    And I should see "80.00 %"
index f3fa6f6..a3c75bd 100644 (file)
@@ -21,6 +21,19 @@ class grade_export_xls extends grade_export {
 
     public $plugin = 'xls';
 
+    /**
+     * Constructor should set up all the private variables ready to be pulled
+     * @param object $course
+     * @param int $groupid id of selected group, 0 means all
+     * @param stdClass $formdata The validated data from the grade export form.
+     */
+    public function __construct($course, $groupid, $formdata) {
+        parent::__construct($course, $groupid, $formdata);
+
+        // Overrides.
+        $this->usercustomfields = true;
+    }
+
     /**
      * To be implemented by child classes
      */
index ae7b48d..748b210 100644 (file)
@@ -1,6 +1,14 @@
 This files describes API changes in /grade/report/*,
 information provided here is intended especially for developers.
 
+=== 2.6.5, 2.7.2 ===
+
+* The callback function grade_report_*_profilereport now takes one more parameter $viewasuser. This parameter
+  is set to true when the report must be viewed as the user whose grades are being displayed. For instance,
+  when a mentor/parent is viewing the report, they should see the same grades, not more, not less. When the
+  setting is set to false (default), the capability checks, visibility and access levels are using the
+  currently logged in user.
+
 === 2.6 ===
 * grade_report_grader::get_toggles_html() and grade_report_grader::print_toggle()
   can not be used any more
index 930a693..29eb8b6 100644 (file)
@@ -151,14 +151,34 @@ class grade_report_user extends grade_report {
     public $baseurl;
     public $pbarurl;
 
+    /**
+     * The modinfo object to be used.
+     *
+     * @var course_modinfo
+     */
+    protected $modinfo = null;
+
+    /**
+     * View as user.
+     *
+     * When this is set to true, the visibility checks, and capability checks will be
+     * applied to the user whose grades are being displayed. This is very useful when
+     * a mentor/parent is viewing the report of their mentee because they need to have
+     * access to the same information, but not more, not less.
+     *
+     * @var boolean
+     */
+    protected $viewasuser = false;
+
     /**
      * Constructor. Sets local copies of user preferences and initialises grade_tree.
      * @param int $courseid
      * @param object $gpr grade plugin return tracking object
      * @param string $context
      * @param int $userid The id of the user
+     * @param bool $viewasuser Set this to true when the current user is a mentor/parent of the targetted user.
      */
-    public function __construct($courseid, $gpr, $context, $userid) {
+    public function __construct($courseid, $gpr, $context, $userid, $viewasuser = null) {
         global $DB, $CFG;
         parent::__construct($courseid, $gpr, $context);
 
@@ -174,6 +194,8 @@ class grade_report_user extends grade_report {
         $this->showlettergrade = grade_get_setting($this->courseid, 'report_user_showlettergrade', !empty($CFG->grade_report_user_showlettergrade));
         $this->showaverage     = grade_get_setting($this->courseid, 'report_user_showaverage',     !empty($CFG->grade_report_user_showaverage));
 
+        $this->viewasuser = $viewasuser;
+
         // The default grade decimals is 2
         $defaultdecimals = 2;
         if (property_exists($CFG, 'grade_decimalpoints')) {
@@ -203,11 +225,19 @@ class grade_report_user extends grade_report {
 
         $this->tabledata = array();
 
-        $this->canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($this->courseid));
-
-        // get the user (for full name)
+        // Get the user (for full name).
         $this->user = $DB->get_record('user', array('id' => $userid));
 
+        // What user are we viewing this as?
+        $coursecontext = context_course::instance($this->courseid);
+        if ($viewasuser) {
+            $this->modinfo = new course_modinfo($this->course, $this->user->id);
+            $this->canviewhidden = has_capability('moodle/grade:viewhidden', $coursecontext, $this->user->id);
+        } else {
+            $this->modinfo = $this->gtree->modinfo;
+            $this->canviewhidden = has_capability('moodle/grade:viewhidden', $coursecontext);
+        }
+
         // base url for sorting by first/last name
         $this->baseurl = $CFG->wwwroot.'/grade/report?id='.$courseid.'&amp;userid='.$userid;
         $this->pbarurl = $this->baseurl;
@@ -363,7 +393,7 @@ class grade_report_user extends grade_report {
                 // The grade object can be marked visible but still be hidden if
                 // the student cannot see the activity due to conditional access
                 // and it's set to be hidden entirely.
-                $instances = $this->gtree->modinfo->get_instances_of($grade_object->itemmodule);
+                $instances = $this->modinfo->get_instances_of($grade_object->itemmodule);
                 if (!empty($instances[$grade_object->iteminstance])) {
                     $cm = $instances[$grade_object->iteminstance];
                     if (!$cm->uservisible) {
@@ -904,7 +934,14 @@ function grade_report_user_settings_definition(&$mform) {
     $mform->addHelpButton('report_user_showtotalsifcontainhidden', 'hidetotalifhiddenitems', 'grades');
 }
 
-function grade_report_user_profilereport($course, $user) {
+/**
+ * Profile report callback.
+ *
+ * @param object $course The course.
+ * @param object $user The user.
+ * @param boolean $viewasuser True when we are viewing this as the targetted user sees it.
+ */
+function grade_report_user_profilereport($course, $user, $viewasuser = false) {
     global $OUTPUT;
     if (!empty($course->showgrades)) {
 
@@ -916,7 +953,7 @@ function grade_report_user_profilereport($course, $user) {
         /// return tracking object
         $gpr = new grade_plugin_return(array('type'=>'report', 'plugin'=>'user', 'courseid'=>$course->id, 'userid'=>$user->id));
         // Create a report instance
-        $report = new grade_report_user($course->id, $gpr, $context, $user->id);
+        $report = new grade_report_user($course->id, $gpr, $context, $user->id, $viewasuser);
 
         // print the page
         echo '<div class="grade-report-user">'; // css fix to share styles with real report page
index fe817d3..8f14b2b 100644 (file)
@@ -21,16 +21,27 @@ Feature: We can enter in grades and view reports from the gradebook
     And I follow "Course 1"
     And I turn editing mode on
     And I add a "Assignment" to section "1" and I fill the form with:
-      | Assignment name | Test assignment name |
+      | Assignment name | Test assignment name 1 |
+      | Description | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1 |
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name 2 |
       | Description | Submit your online text |
       | assignsubmission_onlinetext_enabled | 1 |
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
-    And I follow "Test assignment name"
+    And I follow "Test assignment name 1"
+    When I press "Add submission"
+    And I set the following fields to these values:
+      | Online text | This is a submission for assignment 1 |
+    And I press "Save changes"
+    Then I should see "Submitted for grading"
+    And I follow "Course 1"
+    And I follow "Test assignment name 2"
     When I press "Add submission"
     And I set the following fields to these values:
-      | Online text | This is a submission |
+      | Online text | This is a submission for assignment 2 |
     And I press "Save changes"
     Then I should see "Submitted for grading"
     And I log out
@@ -38,7 +49,8 @@ Feature: We can enter in grades and view reports from the gradebook
     And I follow "Course 1"
     And I follow "Grades"
     And I turn editing mode on
-    And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name"
+    And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name 1"
+    And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2"
     And I press "Save changes"
 
   @javascript
@@ -52,9 +64,17 @@ Feature: We can enter in grades and view reports from the gradebook
     And I log in as "student1"
     And I follow "Course 1"
     And I follow "Grades"
-    And I should see "80.00" in the "Test assignment name" "table_row"
+    Then the following should exist in the "user-grade" table:
+      | Grade item | Grade | Range | Percentage |
+      | Test assignment name 1 | 80.00 | 0–100 | 80.00 % |
+      | Test assignment name 2 | 90.00 | 0–100 | 90.00 % |
+      | Course total | 85.00 | 0–100 | 85.00 % |
+    And the following should not exist in the "user-grade" table:
+      | Grade item | Grade | Range | Percentage |
+      | Course total | 90.00 | 0–110 | 90.00 % |
     And I set the field "Grade report" to "Overview report"
-    And I should see "80.00" in the "overview-grade" "table"
+    And "C1" row "Grade" column of "overview-grade" table should contain "85.00"
+    And "C1" row "Grade" column of "overview-grade" table should not contain "90.00"
 
   @javascript
   Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
@@ -72,5 +92,13 @@ Feature: We can enter in grades and view reports from the gradebook
     And I log in as "student1"
     And I follow "Course 1"
     And I follow "Grades"
-    Then I should see "0.72" in the "Test assignment name" "table_row"
-    And I should not see "0.72%" in the "Test assignment name" "table_row"
+    Then the following should exist in the "user-grade" table:
+      | Grade item | Weight | Grade | Range | Percentage |
+      | Test assignment name 1 | 0.72 | 80.00 | 0–100 | 80.00 % |
+      | Test assignment name 2 | 1.00 | 90.00 | 0–100 | 90.00 % |
+      | Course total | - | 85.81 | 0–100 | 85.81 % |
+    And the following should not exist in the "user-grade" table:
+      | Grade item | Weight | Percentage |
+      | Test assignment name 1 | 0.72% | 0.72% |
+      | Test assignment name 2 | 1.00% | 1.00% |
+      | Course total | 1.00% | 1.00% |
index ea791de..c338911 100644 (file)
@@ -375,7 +375,9 @@ function groups_update_group($data, $editform = false, $editoroptions = false) {
     $context = context_course::instance($data->courseid);
 
     $data->timemodified = time();
-    $data->name         = trim($data->name);
+    if (isset($data->name)) {
+        $data->name = trim($data->name);
+    }
     if (isset($data->idnumber)) {
         $data->idnumber = trim($data->idnumber);
         if (($existing = groups_get_group_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
@@ -420,7 +422,9 @@ function groups_update_group($data, $editform = false, $editoroptions = false) {
 function groups_update_grouping($data, $editoroptions=null) {
     global $DB;
     $data->timemodified = time();
-    $data->name         = trim($data->name);
+    if (isset($data->name)) {
+        $data->name = trim($data->name);
+    }
     if (isset($data->idnumber)) {
         $data->idnumber = trim($data->idnumber);
         if (($existing = groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) && $existing->id != $data->id) {
index 85dde95..e38b2db 100644 (file)
@@ -110,6 +110,9 @@ XPATH
         , 'filemanager' => <<<XPATH
 //div[contains(concat(' ', normalize-space(@class), ' '), ' ffilemanager ')]
     /descendant::input[@id = //label[contains(normalize-space(string(.)), %locator%)]/@for]
+XPATH
+        , 'table' => <<<XPATH
+.//table[(./@id = %locator% or contains(.//caption, %locator%) or contains(concat(' ', normalize-space(@class), ' '), %locator% ))]
 XPATH
     );
 
index 734d852..62e88bf 100644 (file)
@@ -20,6 +20,9 @@
  * This is based on the memcached code. It lacks some features, such as
  * locking options, but appears to work in practice.
  *
+ * Note: You may need to manually configure redundancy and fail-over
+ * if you specify multiple servers.
+ *
  * @package core
  * @copyright 2014 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -37,6 +40,13 @@ defined('MOODLE_INTERNAL') || die();
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class memcache extends handler {
+    /** @var string $savepath save_path string  */
+    protected $savepath;
+    /** @var array $servers list of servers parsed from save_path */
+    protected $servers;
+    /** @var int $acquiretimeout how long to wait for session lock */
+    protected $acquiretimeout = 120;
+
     /**
      * Creates new instance of handler.
      */
@@ -119,28 +129,39 @@ class memcache extends handler {
      * @return bool true if session found.
      */
     public function session_exists($sid) {
-        if (!$this->servers) {
-            return false;
+        $result = false;
+
+        foreach ($this->get_memcaches() as $memcache) {
+            if ($result === false) {
+                $value = $memcache->get($sid);
+                if ($value !== false) {
+                    $result = true;
+                }
+            }
+            $memcache->close();
         }
 
-        $memcache = $this->get_memcache();
-        $value = $memcache->get($sid);
-        $memcache->close();
-
-        return ($value !== false);
+        return $result;
     }
 
     /**
-     * Gets the memcache object with all the servers added to it.
+     * Gets the Memcache objects, one for each server.
+     * The connects must be closed manually after use.
+     *
+     * Note: the servers are not automatically synchronised
+     *       when accessed via Memcache class, it needs to be
+     *       done manually by accessing all configured servers.
      *
-     * @return \Memcache Initialised memcache object
+     * @return \Memcache[] Array of initialised memcache objects
      */
-    protected function get_memcache() {
-        $memcache = new \Memcache();
+    protected function get_memcaches() {
+        $result = array();
         foreach ($this->servers as $server) {
+            $memcache = new \Memcache();
             $memcache->addServer($server[0], $server[1]);
+            $result[] = $memcache;
         }
-        return $memcache;
+        return $result;
     }
 
     /**
@@ -152,18 +173,22 @@ class memcache extends handler {
             return;
         }
 
-        $memcache = $this->get_memcache();
+        $memcaches = $this->get_memcaches();
 
         // Note: this can be significantly improved by fetching keys from memcache,
         // but we need to make sure we are not deleting somebody else's sessions.
 
         $rs = $DB->get_recordset('sessions', array(), 'id DESC', 'id, sid');
         foreach ($rs as $record) {
-            $memcache->delete($record->sid);
+            foreach ($memcaches as $memcache) {
+                $memcache->delete($record->sid);
+            }
         }
         $rs->close();
 
-        $memcache->close();
+        foreach ($memcaches as $memcache) {
+            $memcache->close();
+        }
     }
 
     /**
@@ -172,12 +197,9 @@ class memcache extends handler {
      * @param string $sid PHP session ID
      */
     public function kill_session($sid) {
-        if (!$this->servers) {
-            return;
+        foreach ($this->get_memcaches() as $memcache) {
+            $memcache->delete($sid);
+            $memcache->close();
         }
-
-        $memcache = $this->get_memcache();
-        $memcache->delete($sid);
-        $memcache->close();
     }
 }
index 73392cb..f5722db 100644 (file)
@@ -137,12 +137,22 @@ class memcached extends handler {
             return false;
         }
 
-        $memcached = new \Memcached();
-        $memcached->addServers($this->servers);
-        $value = $memcached->get($this->prefix.$sid);
-        $memcached->quit();
+        // Go through the list of all servers because
+        // we do not know where the session handler put the
+        // data.
+
+        foreach ($this->servers as $server) {
+            list($host, $port) = $server;
+            $memcached = new \Memcached();
+            $memcached->addServer($host, $port);
+            $value = $memcached->get($this->prefix . $sid);
+            $memcached->quit();
+            if ($value !== false) {
+                return true;
+            }
+        }
 
-        return ($value !== false);
+        return false;
     }
 
     /**
@@ -155,19 +165,32 @@ class memcached extends handler {
             return;
         }
 
-        $memcached = new \Memcached();
-        $memcached->addServers($this->servers);
+        // Go through the list of all servers because
+        // we do not know where the session handler put the
+        // data.
+
+        $memcacheds = array();
+        foreach ($this->servers as $server) {
+            list($host, $port) = $server;
+            $memcached = new \Memcached();
+            $memcached->addServer($host, $port);
+            $memcacheds[] = $memcached;
+        }
 
         // Note: this can be significantly improved by fetching keys from memcached,
         //       but we need to make sure we are not deleting somebody else's sessions.
 
         $rs = $DB->get_recordset('sessions', array(), 'id DESC', 'id, sid');
         foreach ($rs as $record) {
-            $memcached->delete($this->prefix.$record->sid);
+            foreach ($memcacheds as $memcached) {
+                $memcached->delete($this->prefix . $record->sid);
+            }
         }
         $rs->close();
 
-        $memcached->quit();
+        foreach ($memcacheds as $memcached) {
+            $memcached->quit();
+        }
     }
 
     /**
@@ -179,11 +202,17 @@ class memcached extends handler {
             return;
         }
 
-        $memcached = new \Memcached();
-        $memcached->addServers($this->servers);
-        $memcached->delete($this->prefix.$sid);
+        // Go through the list of all servers because
+        // we do not know where the session handler put the
+        // data.
 
-        $memcached->quit();
+        foreach ($this->servers as $server) {
+            list($host, $port) = $server;
+            $memcached = new \Memcached();
+            $memcached->addServer($host, $port);
+            $memcached->delete($this->prefix . $sid);
+            $memcached->quit();
+        }
     }
 
 }
index f0926fc..bdf55be 100644 (file)
@@ -48,7 +48,8 @@ class send_new_user_passwords_task extends scheduled_task {
         if ($DB->count_records('user_preferences', array('name' => 'create_password', 'value' => '1'))) {
             mtrace('Creating passwords for new users...');
             $usernamefields = get_all_user_name_fields(true, 'u');
-            $newusers = $DB->get_recordset_sql("SELECT u.id as id, u.email,
+            $newusers = $DB->get_recordset_sql("SELECT u.id as id, u.email, u.auth, u.deleted,
+                                                     u.suspended, u.emailstop, u.mnethostid, u.mailformat,
                                                      $usernamefields, u.username, u.lang,
                                                      p.id as prefid
                                                 FROM {user} u
index 9dac524..25ec8c3 100644 (file)
@@ -1608,6 +1608,10 @@ class core_ddl_testcase extends database_driver_testcase {
                   JOIN {test_temp} t ON t.name = n.name";
         $records = $DB->get_records_sql($sql);
         $this->assertCount(1, $records);
+
+        // Drop temp table.
+        $dbman->drop_table($table2);
+        $this->assertFalse($dbman->table_exists('test_temp'));
     }
 
     public function test_concurrent_temp_tables() {
diff --git a/lib/editor/atto/autosave-ajax.php b/lib/editor/atto/autosave-ajax.php
new file mode 100644 (file)
index 0000000..36dcc06
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Save and load draft text while a user is still editing a form.
+ *
+ * @package    editor_atto
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('AJAX_SCRIPT', true);
+
+require_once(dirname(__FILE__) . '/../../../config.php');
+
+$contextid = required_param('contextid', PARAM_INT);
+$elementid = required_param('elementid', PARAM_ALPHANUMEXT);
+$pagehash = required_param('pagehash', PARAM_ALPHANUMEXT);
+$pageinstance = required_param('pageinstance', PARAM_ALPHANUMEXT);
+$now = time();
+// This is the oldest time any autosave text will be recovered from.
+// This is so that there is a good chance the draft files will still exist (there are many variables so
+// this is impossible to guarantee).
+$before = $now - 60*60*24*4;
+
+list($context, $course, $cm) = get_context_info_array($contextid);
+$PAGE->set_url('/lib/editor/atto/autosave-ajax.php');
+$PAGE->set_context($context);
+
+require_login($course, false, $cm);
+require_sesskey();
+
+$action = required_param('action', PARAM_ALPHA);
+
+if ($action === 'save') {
+    $drafttext = required_param('drafttext', PARAM_RAW);
+    $params = array('elementid' => $elementid,
+                    'userid' => $USER->id,
+                    'pagehash' => $pagehash,
+                    'contextid' => $contextid);
+
+    $record = $DB->get_record('editor_atto_autosave', $params);
+    if ($record && $record->pageinstance != $pageinstance) {
+        print_error('concurrent access from the same user is not supported');
+        die();
+    }
+
+    if (!$record) {
+        $record = new stdClass();
+        $record->elementid = $elementid;
+        $record->userid = $USER->id;
+        $record->pagehash = $pagehash;
+        $record->contextid = $contextid;
+        $record->drafttext = $drafttext;
+        $record->pageinstance = $pageinstance;
+        $record->timemodified = $now;
+
+        $DB->insert_record('editor_atto_autosave', $record);
+
+        // No response means no error.
+        die();
+    } else {
+        $record->drafttext = $drafttext;
+        $record->timemodified = time();
+        $DB->update_record('editor_atto_autosave', $record);
+
+        // No response means no error.
+        die();
+    }
+} else if ($action == 'resume') {
+    $params = array('elementid' => $elementid,
+                    'userid' => $USER->id,
+                    'pagehash' => $pagehash,
+                    'contextid' => $contextid);
+
+    $newdraftid = required_param('draftid', PARAM_INT);
+
+    $record = $DB->get_record('editor_atto_autosave', $params);
+
+    if (!$record) {
+        $record = new stdClass();
+        $record->elementid = $elementid;
+        $record->userid = $USER->id;
+        $record->pagehash = $pagehash;
+        $record->contextid = $contextid;
+        $record->pageinstance = $pageinstance;
+        $record->pagehash = $pagehash;
+        $record->draftid = $newdraftid;
+        $record->timemodified = time();
+        $record->drafttext = '';
+
+        $DB->insert_record('editor_atto_autosave', $record);
+
+        // No response means no error.
+        die();
+    } else {
+        // Copy all draft files from the old draft area.
+        $usercontext = context_user::instance($USER->id);
+        $stale = $record->timemodified < $before;
+        require_once($CFG->libdir . '/filelib.php');
+
+        // This function copies all the files in one draft area, to another area (in this case it's
+        // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
+        $newdrafttext = file_save_draft_area_files($record->draftid,
+                                                   $usercontext->id,
+                                                   'user',
+                                                   'draft',
+                                                   $newdraftid,
+                                                   array(),
+                                                   $record->drafttext);
+
+        // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
+        $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
+                                                     'draftfile.php',
+                                                     $usercontext->id,
+                                                     'user',
+                                                     'draft',
+                                                     $newdraftid);
+        $record->drafttext = $newdrafttext;
+
+        $record->pageinstance = $pageinstance;
+        $record->draftid = $newdraftid;
+        $record->timemodified = time();
+        $DB->update_record('editor_atto_autosave', $record);
+
+        // A response means the draft has been restored and here is the auto-saved text.
+        if (!$stale) {
+            echo $record->drafttext;
+        }
+        die();
+    }
+} else if ($action == 'reset') {
+    $params = array('elementid' => $elementid,
+                    'userid' => $USER->id,
+                    'pagehash' => $pagehash,
+                    'contextid' => $contextid);
+
+    $DB->delete_records('editor_atto_autosave', $params);
+    die();
+}
+
+print_error('invalidarguments');
diff --git a/lib/editor/atto/db/install.xml b/lib/editor/atto/db/install.xml
new file mode 100755 (executable)
index 0000000..8d17ee4
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="lib/editor/atto/db" VERSION="20140819" COMMENT="XMLDB file for Moodle lib/editor/atto"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="editor_atto_autosave" COMMENT="Draft text that is auto-saved every 5 seconds while an editor is open.">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
+        <FIELD NAME="elementid" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The unique id for the text editor in the form."/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The contextid that the form was loaded with."/>
+        <FIELD NAME="pagehash" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The HTML DOM id of the page that loaded the form."/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the user that loaded the form."/>
+        <FIELD NAME="drafttext" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The draft text"/>
+        <FIELD NAME="draftid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Optional draft area id containing draft files."/>
+        <FIELD NAME="pageinstance" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The browser tab instance that last saved the draft text. This is to prevent multiple tabs from the same user saving different text alternately."/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Store the last modified time for the auto save text."/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+        <KEY NAME="autosave_uniq_key" TYPE="unique" FIELDS="elementid, contextid, userid, pagehash" COMMENT="Unique key for the user in the form in the page."/>
+      </KEYS>
+    </TABLE>
+  </TABLES>
+</XMLDB>
\ No newline at end of file
index c861110..42ef164 100644 (file)
@@ -53,6 +53,49 @@ function xmldb_editor_atto_upgrade($oldversion) {
 
     // Moodle v2.7.0 release upgrade line.
     // Put any upgrade step following this.
+    if ($oldversion < 2014081400) {
+
+        // Define table editor_atto_autosave to be created.
+        $table = new xmldb_table('editor_atto_autosave');
+
+        // Adding fields to table editor_atto_autosave.
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('elementid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('contextid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('pagehash', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+        $table->add_field('drafttext', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+        $table->add_field('draftid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
+        $table->add_field('pageinstance', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table editor_atto_autosave.
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $table->add_key('autosave_uniq_key', XMLDB_KEY_UNIQUE, array('elementid', 'contextid', 'userid', 'pagehash'));
+
+        // Conditionally launch create table for editor_atto_autosave.
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Atto savepoint reached.
+        upgrade_plugin_savepoint(true, 2014081400, 'editor', 'atto');
+    }
+
+    if ($oldversion < 2014081900) {
+
+        // Define field timemodified to be added to editor_atto_autosave.
+        $table = new xmldb_table('editor_atto_autosave');
+        $field = new xmldb_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'pageinstance');
+
+        // Conditionally launch add field timemodified.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Atto savepoint reached.
+        upgrade_plugin_savepoint(true, 2014081900, 'editor', 'atto');
+    }
+
 
     return true;
 }
index e9739a9..d1c2b68 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['autosavefailed'] = 'Could not connect to the server. If you submit this page now, your changes may be lost.';
+$string['autosavefrequency'] = 'Autosave frequency (seconds).';
+$string['autosavefrequency_desc'] = 'This is the number of seconds between auto save attempts. Atto will automatically save the text in the editor according to this setting, so that text can be automatically restored when the same user returns to the same form.';
+$string['autosavesucceeded'] = 'Draft saved.';
 $string['errorcannotparseline'] = 'The line \'{$a}\' is not in the correct format.';
 $string['errorgroupisusedtwice'] = 'The group \'{$a}\' is defined twice; group names must be unique.';
 $string['errornopluginsorgroupsfound'] = 'No plugins or groups found; please add some groups and plugins.';
@@ -31,8 +35,12 @@ $string['pluginname'] = 'Atto HTML editor';
 $string['subplugintype_atto'] = 'Atto plugin';
 $string['subplugintype_atto_plural'] = 'Atto plugins';
 $string['settings'] = 'Atto toolbar settings';
+$string['textrecovered'] = 'A draft version of this text was automatically restored.';
 $string['toolbarconfig'] = 'Toolbar config';
 $string['toolbarconfig_desc'] = 'The list of plugins and the order they are displayed can be configured here. The configuration consists of groups (one per line) followed by the ordered list of plugins for that group. The group is separated from the plugins with an equals sign and the plugins are separated with commas. The group names must be unique and should indicate what the buttons have in common. Button and group names should not be repeated and may only contain alphanumeric characters.';
 $string['editor_command_keycode'] = 'Cmd + {$a}';
 $string['editor_control_keycode'] = 'Ctrl + {$a}';
 $string['plugin_title_shortcut'] = '{$a->title} [{$a->shortcut}]';
+$string['recover'] = 'Recover';
+$string['infostatus'] = 'Information';
+$string['warningstatus'] = 'Warning';
index 7890323..00cd928 100644 (file)
@@ -125,7 +125,14 @@ class atto_texteditor extends texteditor {
                 'editor_command_keycode',
                 'editor_control_keycode',
                 'plugin_title_shortcut',
+                'textrecovered',
+                'autosavefailed',
+                'autosavesucceeded'
             ), 'editor_atto');
+        $PAGE->requires->strings_for_js(array(
+                'warning',
+                'info'
+            ), 'moodle');
         $PAGE->requires->yui_module($modules,
                                     'Y.M.editor_atto.Editor.init',
                                     array($this->get_init_params($elementid, $options, $fpoptions, $jsplugins)));
@@ -146,15 +153,24 @@ class atto_texteditor extends texteditor {
         $strtime        = get_string('strftimetime');
         $strdate        = get_string('strftimedaydate');
         $lang           = current_language();
+        $autosave       = true;
+        $autosavefrequency = get_config('editor_atto', 'autosavefrequency');
+        if (isset($options['autosave'])) {
+            $autosave       = $options['autosave'];
+        }
         $contentcss     = $PAGE->theme->editor_css_url()->out(false);
 
         $params = array(
             'elementid' => $elementid,
             'content_css' => $contentcss,
+            'contextid' => $options['context']->id,
+            'autosaveEnabled' => $autosave,
+            'autosaveFrequency' => $autosavefrequency,
             'language' => $lang,
             'directionality' => $directionality,
             'filepickeroptions' => array(),
-            'plugins' => $plugins
+            'plugins' => $plugins,
+            'pageHash' => sha1($PAGE->url)
         );
         if ($fpoptions) {
             $params['filepickeroptions'] = $fpoptions;
index 4e9164a..48a9170 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-debug.js differ
index 1a1ad36..d346265 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button-min.js differ
index 4e9164a..48a9170 100644 (file)
Binary files a/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js and b/lib/editor/atto/plugins/image/yui/build/moodle-atto_image-button/moodle-atto_image-button.js differ
index a8724c9..b8f286b 100644 (file)
@@ -81,7 +81,7 @@ var CSS = {
             name: 'right',
             str: 'alignment_right',
             value: 'float',
-            margin: '0 0 .5em 0'
+            margin: '0 0 0 .5em'
         }, {
             name: 'customstyle',
             str: 'customstyle',
index 9236504..6fd8654 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-debug.js differ
index 818481e..95c2b3e 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button-min.js differ
index 9236504..6fd8654 100644 (file)
Binary files a/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js and b/lib/editor/atto/plugins/undo/yui/build/moodle-atto_undo-button/moodle-atto_undo-button.js differ
index 0a5baf5..983d746 100644 (file)
@@ -278,7 +278,7 @@ Y.namespace('M.atto_undo').Button = Y.Base.create('button', Y.M.editor_atto.Edit
      * @private
      */
     _changeListener: function(e) {
-        if (e.event.type.indexOf('key') !== -1) {
+        if (e.event && e.event.type.indexOf('key') !== -1) {
             // These are the 4 arrow keys.
             if ((e.event.keyCode !== 39) &&
                     (e.event.keyCode !== 37) &&
index f7d61dc..b0dbc5e 100644 (file)
@@ -48,6 +48,13 @@ other = html';
 
     $settings->add($setting);
 }
+
+$name = new lang_string('autosavefrequency', 'editor_atto');
+$desc = new lang_string('autosavefrequency_desc', 'editor_atto');
+$default = 60;
+$setting = new admin_setting_configduration('editor_atto/autosavefrequency', $name, $desc, $default);
+$settings->add($setting);
+
 $ADMIN->add('editoratto', $settings);
 
 foreach (core_plugin_manager::instance()->get_plugins_of_type('atto') as $plugin) {
index 284ba8e..425642f 100644 (file)
@@ -181,3 +181,35 @@ div.editor_atto_content:hover .atto_control {
     padding-top: 5px;
     padding-bottom: 5px;
 }
+
+.editor_atto_notification {
+    position: relative;
+    bottom: 0px;
+    height: 1.5em;
+    margin-top: -2.5em;
+    margin-left: 1px;
+    margin-right: 1px;
+    margin-bottom: 1em;
+    cursor: pointer;
+}
+.editor_atto_notification .atto_info {
+    display: inline-block;
+    background-color: #F2F2F2;
+    padding: 0.5em;
+    padding-left: 1em;
+    padding-right: 1em;
+    border-top-right-radius: 1em;
+}
+.editor_atto_notification .atto_warning {
+    display: inline-block;
+    background-color: #FFD700;
+    padding: 0.5em;
+    padding-left: 1em;
+    padding-right: 1em;
+    border-top-right-radius: 1em;
+}
+.dir-rtl .editor_atto_notification .atto_info,
+.dir-rtl .editor_atto_notification .atto_warning {
+    border-top-right-radius: 0;
+    border-top-left-radius: 1em;
+}
index 89723d4..0edaf3b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014051200;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2014081900;        // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2014050800;        // Requires this Moodle version.
 $plugin->component = 'editor_atto';  // Full name of the plugin (used for diagnostics).
index c3a6a7a..40cc593 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index 38b4105..068aab8 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index b2d7442..cb7326d 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index 2b6c7f1..f781a68 100644 (file)
@@ -4,7 +4,9 @@
         "moodle-editor_atto-editor": {
             "jsfiles": [
                 "editor.js",
+                "notify.js",
                 "textarea.js",
+                "autosave.js",
                 "clean.js",
                 "toolbar.js",
                 "toolbar-keyboardnav.js",
diff --git a/lib/editor/atto/yui/src/editor/js/autosave.js b/lib/editor/atto/yui/src/editor/js/autosave.js
new file mode 100644 (file)
index 0000000..33b0b88
--- /dev/null
@@ -0,0 +1,267 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A autosave function for the Atto editor.
+ *
+ * @module     moodle-editor_atto-autosave
+ * @submodule  autosave-base
+ * @package    editor_atto
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var SUCCESS_MESSAGE_TIMEOUT = 5000,
+    RECOVER_MESSAGE_TIMEOUT = 60000,
+    LOGNAME_AUTOSAVE = 'moodle-editor_atto-editor-autosave';
+
+function EditorAutosave() {}
+
+EditorAutosave.ATTRS= {
+    /**
+     * Enable/Disable auto save for this instance.
+     *
+     * @attribute autosaveEnabled
+     * @type Boolean
+     * @writeOnce
+     */
+    autosaveEnabled: {
+        value: true,
+        writeOnce: true
+    },
+
+    /**
+     * The time between autosaves (in seconds).
+     *
+     * @attribute autosaveFrequency
+     * @type Number
+     * @default 60
+     * @writeOnce
+     */
+    autosaveFrequency: {
+        value: 60,
+        writeOnce: true
+    },
+
+    /**
+     * Unique hash for this page instance. Calculated from $PAGE->url in php.
+     *
+     * @attribute pageHash
+     * @type String
+     * @writeOnce
+     */
+    pageHash: {
+        value: '',
+        writeOnce: true
+    },
+
+    /**
+     * The relative path to the ajax script.
+     *
+     * @attribute autosaveAjaxScript
+     * @type String
+     * @default '/lib/editor/atto/autosave-ajax.php'
+     * @readOnly
+     */
+    autosaveAjaxScript: {
+        value: '/lib/editor/atto/autosave-ajax.php',
+        readOnly: true
+    }
+};
+
+EditorAutosave.prototype = {
+
+    /**
+     * The text that was auto saved in the last request.
+     *
+     * @property lastText
+     * @type string
+     */
+    lastText: null,
+
+    /**
+     * Autosave instance.
+     *
+     * @property autosaveInstance
+     * @type string
+     */
+    autosaveInstance: null,
+
+    /**
+     * Initialize the autosave process
+     *
+     * @method setupAutosave
+     * @chainable
+     */
+    setupAutosave: function() {
+        var draftid = -1,
+            optiontype = null,
+            options = this.get('filepickeroptions');
+
+        if (!this.get('autosaveEnabled')) {
+            // Autosave disabled for this instance.
+            return;
+        }
+
+        this.autosaveInstance = Y.stamp(this);
+        for (optiontype in options) {
+            if (typeof options[optiontype].itemid !== "undefined") {
+                draftid = options[optiontype].itemid;
+            }
+        }
+
+        // First see if there are any saved drafts.
+        // Make an ajax request.
+        url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
+        params = {
+            sesskey: M.cfg.sesskey,
+            contextid: this.get('contextid'),
+            action: 'resume',
+            drafttext: '',
+            draftid: draftid,
+            elementid: this.get('elementid'),
+            pageinstance: this.autosaveInstance,
+            pagehash: this.get('pageHash')
+        };
+
+        Y.io(url, {
+            method: 'POST',
+            data: params,
+            context: this,
+            on: {
+                success: function(id,o) {
+                    if (typeof o.responseText !== "undefined" &&
+                            o.responseText !== "" &&
+                            o.responseText !== this.textarea.get('value')) {
+                        Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
+                        this.recoverText(o.responseText);
+                        this._fireSelectionChanged();
+                    }
+                }
+            }
+        });
+
+        // Now setup the timer for periodic saves.
+
+        var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
+        Y.later(delay, this, this.saveDraft, false, true);
+
+        // Now setup the listener for form submission.
+        this.textarea.ancestor('form').on('submit', this.resetAutosave, this);
+        return this;
+    },
+
+    /**
+     * Clear the autosave text because the form was submitted normally.
+     *
+     * @method resetAutosave
+     * @chainable
+     */
+    resetAutosave: function() {
+        // Make an ajax request to reset the autosaved text.
+        url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
+        params = {
+            sesskey: M.cfg.sesskey,
+            contextid: this.get('contextid'),
+            action: 'reset',
+            elementid: this.get('elementid'),
+            pageinstance: this.autosaveInstance,
+            pagehash: this.get('pageHash')
+        };
+
+        Y.io(url, {
+            method: 'POST',
+            data: params,
+            sync: true
+        });
+        return this;
+    },
+
+
+    /**
+     * Recover a previous version of this text and show a message.
+     *
+     * @method recoverText
+     * @param {String} text
+     * @chainable
+     */
+    recoverText: function(text) {
+        this.editor.setHTML(text);
+        this.saveSelection();
+        this.updateOriginal();
+        this.lastText = text;
+
+        this.showMessage(M.util.get_string('textrecovered', 'editor_atto'), NOTIFY_INFO, RECOVER_MESSAGE_TIMEOUT);
+
+        return this;
+    },
+
+    /**
+     * Save a single draft via ajax.
+     *
+     * @method saveDraft
+     * @chainable
+     */
+    saveDraft: function() {
+        this.updateOriginal();
+        var newText = this.textarea.get('value');
+
+        if (newText !== this.lastText) {
+            Y.log('Autosave text', 'debug', LOGNAME_AUTOSAVE);
+
+            // Make an ajax request.
+            url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
+            params = {
+                sesskey: M.cfg.sesskey,
+                contextid: this.get('contextid'),
+                action: 'save',
+                drafttext: newText,
+                elementid: this.get('elementid'),
+                pagehash: this.get('pageHash'),
+                pageinstance: this.autosaveInstance
+            };
+
+            // Reusable error handler - must be passed the correct context.
+            var ajaxErrorFunction = function(code, response) {
+                var errorDuration = parseInt(this.get('autosaveFrequency'), 10) * 1000;
+                Y.log('Error while autosaving text:' + code, 'warn', LOGNAME_AUTOSAVE);
+                Y.log(response, 'warn', LOGNAME_AUTOSAVE);
+                this.showMessage(M.util.get_string('autosavefailed', 'editor_atto'), NOTIFY_WARNING, errorDuration);
+            };
+
+            Y.io(url, {
+                method: 'POST',
+                data: params,
+                context: this,
+                on: {
+                    error: ajaxErrorFunction,
+                    failure: ajaxErrorFunction,
+                    success: function(code, response) {
+                        if (response.response !== "") {
+                            Y.soon(Y.bind(ajaxErrorFunction, this, [code, response]));
+                        } else {
+                            // All working.
+                            this.lastText = newText;
+                            this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'), NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
+                        }
+                    }
+                }
+            });
+        }
+        return this;
+    }
+};
+
+Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosave]);
index 931086c..e03c03f 100644 (file)
@@ -234,6 +234,11 @@ Y.extend(Editor, Y.Base, {
 
         // Setup plugins.
         this.setupPlugins();
+
+        // Initialize the auto-save timer.
+        this.setupAutosave();
+        // Preload the icons for the notifications.
+        this.setupNotifications();
     },
 
     /**
@@ -387,6 +392,18 @@ Y.extend(Editor, Y.Base, {
             writeOnce: true
         },
 
+        /**
+         * The contextid of the form.
+         *
+         * @attribute contextid
+         * @type Integer
+         * @writeOnce
+         */
+        contextid: {
+            value: null,
+            writeOnce: true
+        },
+
         /**
          * Plugins with their configuration.
          *
diff --git a/lib/editor/atto/yui/src/editor/js/notify.js b/lib/editor/atto/yui/src/editor/js/notify.js
new file mode 100644 (file)
index 0000000..49ebc48
--- /dev/null
@@ -0,0 +1,138 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * A notify function for the Atto editor.
+ *
+ * @module     moodle-editor_atto-notify
+ * @submodule  notify
+ * @package    editor_atto
+ * @copyright  2014 Damyon Wiese
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var LOGNAME_NOTIFY = 'moodle-editor_atto-editor-notify',
+    NOTIFY_INFO = 'info',
+    NOTIFY_WARNING = 'warning';
+
+function EditorNotify() {}
+
+EditorNotify.ATTRS= {
+};
+
+EditorNotify.prototype = {
+
+    /**
+     * A single Y.Node for this editor. There is only ever one - it is replaced if a new message comes in.
+     *
+     * @property messageOverlay
+     * @type {Node}
+     */
+    messageOverlay: null,
+
+    /**
+     * A single timer object that can be used to cancel the hiding behaviour.
+     *
+     * @property hideTimer
+     * @type {timer}
+     */
+    hideTimer: null,
+
+    /**
+     * Initialize the notifications.
+     *
+     * @method setupNotifications
+     * @chainable
+     */
+    setupNotifications: function() {
+        var preload1 = new Image(),
+            preload2 = new Image();
+
+        preload1.src = M.util.image_url('i/warning', 'moodle');
+        preload2.src = M.util.image_url('i/info', 'moodle');
+
+        return this;
+    },
+
+    /**
+     * Show a notification in a floaty overlay somewhere in the atto editor text area.
+     *
+     * @method showMessage
+     * @param {String} message The translated message (use get_string)
+     * @param {String} type Must be either "info" or "warning"
+     * @param {Number} timeout Time in milliseconds to show this message for.
+     * @chainable
+     */
+    showMessage: function(message, type, timeout) {
+        var messageTypeIcon = '',
+            intTimeout,
+            bodyContent;
+
+        if (this.messageOverlay === null) {
+            this.messageOverlay = Y.Node.create('<div class="editor_atto_notification"></div>');
+
+            this.messageOverlay.hide(true);
+            this._wrapper.append(this.messageOverlay);
+
+            this.messageOverlay.on('click', function() {
+                this.messageOverlay.hide(true);
+            }, this);
+        }
+
+        if (this.hideTimer !== null) {
+            this.hideTimer.cancel();
+        }
+
+        if (type === NOTIFY_WARNING) {
+   &