Merge branch 'MDL-37641-master' of git://github.com/FMCorz/moodle
authorDamyon Wiese <damyon@moodle.com>
Mon, 25 Feb 2013 07:23:19 +0000 (15:23 +0800)
committerDamyon Wiese <damyon@moodle.com>
Mon, 25 Feb 2013 07:23:19 +0000 (15:23 +0800)
235 files changed:
.jshintrc
README.txt
admin/tool/behat/cli/util.php
admin/tool/behat/tests/behat/nasty_strings.feature [new file with mode: 0644]
backup/import.php
blocks/html/backup/moodle1/lib.php
blocks/navigation/version.php
blocks/navigation/yui/navigation/navigation.js
blocks/recent_activity/block_recent_activity.php
blocks/rss_client/backup/moodle1/lib.php
blocks/settings/lang/en/block_settings.php
blog/edit_form.php
cache/classes/config.php
cache/classes/definition.php
cache/classes/factory.php
cache/classes/helper.php
cache/locallib.php
cache/tests/cache_test.php
cache/tests/fixtures/lib.php
comment/comment_ajax.php
comment/lib.php
config-dist.php
course/dndupload.js
course/lib.php
course/manage.php
course/tests/behat/behat_course.php
course/tests/courselib_test.php
course/yui/toolboxes/toolboxes.js
enrol/cohort/lib.php
enrol/flatfile/adminlib.php [new file with mode: 0644]
enrol/flatfile/db/install.php [new file with mode: 0644]
enrol/flatfile/settings.php
enrol/imsenterprise/lib.php
enrol/imsenterprise/locallib.php
enrol/locallib.php
enrol/manual/ajax.php
enrol/manual/lib.php
enrol/manual/manage.php
enrol/manual/yui/quickenrolment/quickenrolment.js
enrol/paypal/lib.php
enrol/self/lib.php
filter/activitynames/filter.php
grade/report/grader/module.js
group/externallib.php
group/groupings.php
group/tests/externallib_test.php
lang/en/admin.php
lang/en/countries.php
lang/en/grading.php
lang/en/plugin.php
lang/en/rating.php
lib/behat/behat_base.php
lib/behat/form_field/behat_form_editor.php
lib/csslib.php
lib/db/services.php
lib/db/upgrade.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filterlib.php
lib/google/Google_Client.php [new file with mode: 0644]
lib/google/LICENSE [new file with mode: 0644]
lib/google/NOTICE [new file with mode: 0644]
lib/google/README [new file with mode: 0644]
lib/google/auth/Google_AssertionCredentials.php [new file with mode: 0644]
lib/google/auth/Google_Auth.php [new file with mode: 0644]
lib/google/auth/Google_AuthNone.php [new file with mode: 0644]
lib/google/auth/Google_LoginTicket.php [new file with mode: 0644]
lib/google/auth/Google_OAuth2.php [new file with mode: 0644]
lib/google/auth/Google_P12Signer.php [new file with mode: 0644]
lib/google/auth/Google_PemVerifier.php [new file with mode: 0644]
lib/google/auth/Google_Signer.php [new file with mode: 0644]
lib/google/auth/Google_Verifier.php [new file with mode: 0644]
lib/google/cache/Google_ApcCache.php [new file with mode: 0644]
lib/google/cache/Google_Cache.php [new file with mode: 0644]
lib/google/cache/Google_FileCache.php [new file with mode: 0644]
lib/google/cache/Google_MemcacheCache.php [new file with mode: 0644]
lib/google/config.php [new file with mode: 0644]
lib/google/contrib/Google_AdexchangebuyerService.php [new file with mode: 0644]
lib/google/contrib/Google_AdsenseService.php [new file with mode: 0644]
lib/google/contrib/Google_AdsensehostService.php [new file with mode: 0644]
lib/google/contrib/Google_AnalyticsService.php [new file with mode: 0644]
lib/google/contrib/Google_BigqueryService.php [new file with mode: 0644]
lib/google/contrib/Google_BloggerService.php [new file with mode: 0644]
lib/google/contrib/Google_BooksService.php [new file with mode: 0644]
lib/google/contrib/Google_CalendarService.php [new file with mode: 0644]
lib/google/contrib/Google_ComputeService.php [new file with mode: 0644]
lib/google/contrib/Google_CustomsearchService.php [new file with mode: 0644]
lib/google/contrib/Google_DriveService.php [new file with mode: 0644]
lib/google/contrib/Google_FreebaseService.php [new file with mode: 0644]
lib/google/contrib/Google_FusiontablesService.php [new file with mode: 0644]
lib/google/contrib/Google_GanService.php [new file with mode: 0644]
lib/google/contrib/Google_LatitudeService.php [new file with mode: 0644]
lib/google/contrib/Google_LicensingService.php [new file with mode: 0644]
lib/google/contrib/Google_ModeratorService.php [new file with mode: 0644]
lib/google/contrib/Google_Oauth2Service.php [new file with mode: 0644]
lib/google/contrib/Google_OrkutService.php [new file with mode: 0644]
lib/google/contrib/Google_PagespeedonlineService.php [new file with mode: 0644]
lib/google/contrib/Google_PlusMomentsService.php [new file with mode: 0644]
lib/google/contrib/Google_PlusService.php [new file with mode: 0644]
lib/google/contrib/Google_PredictionService.php [new file with mode: 0644]
lib/google/contrib/Google_ShoppingService.php [new file with mode: 0644]
lib/google/contrib/Google_SiteVerificationService.php [new file with mode: 0644]
lib/google/contrib/Google_StorageService.php [new file with mode: 0644]
lib/google/contrib/Google_TaskqueueService.php [new file with mode: 0644]
lib/google/contrib/Google_TasksService.php [new file with mode: 0644]
lib/google/contrib/Google_TranslateService.php [new file with mode: 0644]
lib/google/contrib/Google_UrlshortenerService.php [new file with mode: 0644]
lib/google/contrib/Google_WebfontsService.php [new file with mode: 0644]
lib/google/contrib/Google_YoutubeService.php [new file with mode: 0644]
lib/google/curlio.php [new file with mode: 0644]
lib/google/external/URITemplateParser.php [new file with mode: 0644]
lib/google/io/Google_CacheParser.php [new file with mode: 0644]
lib/google/io/Google_CurlIO.php [new file with mode: 0644]
lib/google/io/Google_HttpRequest.php [new file with mode: 0644]
lib/google/io/Google_IO.php [new file with mode: 0644]
lib/google/io/Google_REST.php [new file with mode: 0644]
lib/google/io/cacerts.pem [new file with mode: 0644]
lib/google/local_config.php [new file with mode: 0644]
lib/google/readme_moodle.txt [new file with mode: 0644]
lib/google/service/Google_BatchRequest.php [new file with mode: 0644]
lib/google/service/Google_MediaFileUpload.php [new file with mode: 0644]
lib/google/service/Google_Model.php [new file with mode: 0644]
lib/google/service/Google_Service.php [new file with mode: 0644]
lib/google/service/Google_ServiceResource.php [new file with mode: 0644]
lib/google/service/Google_Utils.php [new file with mode: 0644]
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/phpunit/classes/util.php
lib/pluginlib.php
lib/setup.php
lib/testing/classes/nasty_strings.php [new file with mode: 0644]
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_transformations.php [new file with mode: 0644]
lib/thirdpartylibs.xml
lib/upgrade.txt
lib/upgradelib.php
mod/assign/externallib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/submission/comments/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/tests/lib_test.php
mod/assign/tests/locallib_test.php
mod/assign/view.php
mod/data/renderer.php
mod/folder/backup/moodle2/backup_folder_stepslib.php
mod/folder/db/install.xml
mod/folder/db/upgrade.php
mod/folder/edit.php
mod/folder/lang/en/folder.php
mod/folder/lib.php
mod/folder/mod_form.php
mod/folder/module.js
mod/folder/renderer.php
mod/folder/version.php
mod/folder/view.php
mod/forum/lang/en/forum.php
mod/forum/search.php
mod/label/db/upgrade.php
mod/label/lib.php
mod/label/version.php
mod/lesson/report.php
mod/lti/locallib.php
mod/quiz/accessrule/openclosedate/rule.php
mod/quiz/accessrule/openclosedate/tests/rule_test.php
mod/quiz/lib.php
mod/quiz/mod_form.php
mod/quiz/version.php
mod/scorm/lang/en/scorm.php
mod/scorm/mod_form.php
mod/scorm/report/basic/report.php
mod/scorm/report/graphs/graph.php
mod/scorm/report/interactions/report.php
notes/delete.php
notes/edit.php
notes/externallib.php
notes/lib.php
notes/tests/externallib_test.php
question/format/blackboard/tests/fixtures/sample_blackboard.dat
question/format/blackboard_six/formatbase.php
question/format/blackboard_six/tests/fixtures/sample_blackboard_pool.dat
question/format/xml/format.php
question/format/xml/tests/fixtures/truefalse.xml
report/courseoverview/index.php
report/courseoverview/settings.php
report/log/locallib.php
repository/googledocs/lang/en/repository_googledocs.php
repository/googledocs/lib.php
repository/googledocs/version.php
tag/coursetagslib.php
theme/base/style/admin.css
theme/base/style/core.css
theme/base/style/user.css
theme/base/version.php
theme/canvas/style/core.css
theme/formal_white/lib.php
theme/formal_white/style/formal_white.css
theme/mymobile/config.php
theme/mymobile/javascript/custom.js
theme/mymobile/javascript/jquery-1.7.1.min.js [deleted file]
theme/mymobile/javascript/jquery-1.8.2.min.js [new file with mode: 0644]
theme/mymobile/javascript/jquery.mobile-1.2.0.js [moved from theme/mymobile/javascript/jquery.mobile-1.1.1.js with 68% similarity]
theme/mymobile/lang/en/theme_mymobile.php
theme/mymobile/layout/general.php
theme/mymobile/pix/ajax-loader.gif [new file with mode: 0644]
theme/mymobile/pix/ajax-loader.png [deleted file]
theme/mymobile/pix/ajax-loader2.png [deleted file]
theme/mymobile/readme_moodle.txt [deleted file]
theme/mymobile/renderers.php
theme/mymobile/settings.php
theme/mymobile/style/core.css
theme/mymobile/style/jmobile11.css [deleted file]
theme/mymobile/style/jmobile11_rtl.css [deleted file]
theme/mymobile/style/jmobile120.css [new file with mode: 0644]
theme/mymobile/style/media.css
theme/mymobile/version.php
theme/upgrade.txt
user/addnote.php
user/externallib.php
user/groupaddnote.php
user/profile.php
user/profile/lib.php
user/tests/externallib_test.php
user/view.php
version.php

index 9b833d9..06de646 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -1,6 +1,5 @@
 {
     "browser":      true,
-    "node":         true,
     "yui":          true,
     "bitwise":      true,
     "curly":        true,
index 245848b..b1017df 100644 (file)
@@ -8,7 +8,7 @@ a few minutes:
 1) Move the Moodle files into your web directory.
 
 2) Create a single database for Moodle to store all
-   it's tables in (or choose an existing database).
+   its tables in (or choose an existing database).
 
 3) Visit your Moodle site with a browser, you should
    be taken to the install.php script, which will lead
index cfd3581..8cba367 100644 (file)
@@ -84,6 +84,7 @@ error_reporting(E_ALL | E_STRICT);
 ini_set('display_errors', '1');
 ini_set('log_errors', '1');
 
+// Getting $CFG data.
 require_once(__DIR__ . '/../../../../config.php');
 
 // CFG->behat_prefix must be set and with value different than CFG->prefix and phpunit_prefix.
@@ -141,6 +142,10 @@ foreach ($vars as $var) {
 $CFG->noemailever = true;
 $CFG->passwordsaltmain = 'moodle';
 
+// Unset cache and temp directories to reset them again with the new $CFG->dataroot.
+unset($CFG->cachedir);
+unset($CFG->tempdir);
+
 // Continues setup.
 define('ABORT_AFTER_CONFIG_CANCEL', true);
 require("$CFG->dirroot/lib/setup.php");
diff --git a/admin/tool/behat/tests/behat/nasty_strings.feature b/admin/tool/behat/tests/behat/nasty_strings.feature
new file mode 100644 (file)
index 0000000..9135798
--- /dev/null
@@ -0,0 +1,60 @@
+@tool_behat
+Feature: Transform steps arguments
+  In order to write tests with complex nasty arguments
+  As a tests writer
+  I need to apply some transformations to the steps arguments
+
+  Background:
+    Given I am on homepage
+    And the following "courses" exists:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And I log in as "admin"
+    And I follow "Admin User"
+    And I follow "Edit profile"
+
+  Scenario: Use nasty strings on steps arguments
+    When I fill in "Surname" with "$NASTYSTRING1"
+    And I fill in "Description" with "$NASTYSTRING2"
+    And I fill in "City/town" with "$NASTYSTRING3"
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "Surname" field should match "$NASTYSTRING1" value
+    And the "City/town" field should match "$NASTYSTRING3" value
+
+  Scenario: Use nasty strings on table nodes
+    When I fill the moodle form with:
+      | Surname | $NASTYSTRING1 |
+      | Description | $NASTYSTRING2 |
+      | City/town | $NASTYSTRING3 |
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "Surname" field should match "$NASTYSTRING1" value
+    And the "City/town" field should match "$NASTYSTRING3" value
+
+  Scenario: Use double quotes
+    When I fill the moodle form with:
+      | First name | va"lue1 |
+      | Description | va\"lue2 |
+    And I fill in "City/town" with "va\"lue3"
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And the "First name" field should match "va\"lue1" value
+    And the "Description" field should match "va\"lue2" value
+    And the "City/town" field should match "va\"lue3" value
+
+  @javascript
+  Scenario: Nasty strings with other contents
+    When I fill in "First name" with "My Firstname $NASTYSTRING1"
+    And I fill the moodle form with:
+      | Surname | My Surname $NASTYSTRING2 |
+    And I press "Update profile"
+    And I follow "Edit profile"
+    Then I should not see "NASTYSTRING"
+    And I should see "My Firstname"
+    And I should see "My Surname"
+    And the "First name" field should match "My Firstname $NASTYSTRING1" value
+    And the "Surname" field should match "My Surname $NASTYSTRING2" value
index 4de77b2..6dd5038 100644 (file)
@@ -11,6 +11,8 @@ require_once($CFG->dirroot . '/backup/util/ui/import_extensions.php');
 $courseid = required_param('id', PARAM_INT);
 // The id of the course we are importing FROM (will only be set if past first stage
 $importcourseid = optional_param('importid', false, PARAM_INT);
+// We just want to check if a search has been run. True if anything is there.
+$searchcourses = optional_param('searchcourses', false, PARAM_BOOL);
 // The target method for the restore (adding or deleting)
 $restoretarget = optional_param('target', backup::TARGET_CURRENT_ADDING, PARAM_INT);
 
@@ -36,7 +38,7 @@ $PAGE->set_pagelayout('incourse');
 $renderer = $PAGE->get_renderer('core','backup');
 
 // Check if we already have a import course id
-if ($importcourseid === false) {
+if ($importcourseid === false || $searchcourses) {
     // Obviously not... show the selector so one can be chosen
     $url = new moodle_url('/backup/import.php', array('id'=>$courseid));
     $search = new import_course_search(array('url'=>$url));
@@ -160,4 +162,4 @@ echo $renderer->progress_bar($backup->get_progress_bar());
 echo $backup->display($renderer);
 $backup->destroy();
 unset($backup);
-echo $OUTPUT->footer();
\ No newline at end of file
+echo $OUTPUT->footer();
index d4a491f..6373a01 100644 (file)
@@ -1,46 +1,46 @@
-<?php\r
-\r
-/**\r
- * Provides support for the conversion of moodle1 backup to the moodle2 format\r
- *\r
- * @package    block_html\r
- * @copyright  2012 Paul Nicholls\r
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r
- */\r
-\r
-defined('MOODLE_INTERNAL') || die();\r
-\r
-/**\r
- * Block conversion handler for html\r
- */\r
-class moodle1_block_html_handler extends moodle1_block_handler {\r
-    private $fileman = null;\r
-    protected function convert_configdata(array $olddata) {\r
-        $instanceid = $olddata['id'];\r
-        $contextid  = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']);\r
-        $configdata = unserialize(base64_decode($olddata['configdata']));\r
-\r
-        // get a fresh new file manager for this instance\r
-        $this->fileman = $this->converter->get_file_manager($contextid, 'block_html');\r
-\r
-        // convert course files embedded in the block content\r
-        $this->fileman->filearea = 'content';\r
-        $this->fileman->itemid   = 0;\r
-        $configdata->text = moodle1_converter::migrate_referenced_files($configdata->text, $this->fileman);\r
-        $configdata->format = FORMAT_HTML;\r
-\r
-        return base64_encode(serialize($configdata));\r
-    }\r
-\r
-    protected function write_inforef_xml($newdata, $data) {\r
-        $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");\r
-        $this->xmlwriter->begin_tag('inforef');\r
-        $this->xmlwriter->begin_tag('fileref');\r
-        foreach ($this->fileman->get_fileids() as $fileid) {\r
-            $this->write_xml('file', array('id' => $fileid));\r
-        }\r
-        $this->xmlwriter->end_tag('fileref');\r
-        $this->xmlwriter->end_tag('inforef');\r
-        $this->close_xml_writer();\r
-    }\r
-}
\ No newline at end of file
+<?php
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package    block_html
+ * @copyright  2012 Paul Nicholls
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Block conversion handler for html
+ */
+class moodle1_block_html_handler extends moodle1_block_handler {
+    private $fileman = null;
+    protected function convert_configdata(array $olddata) {
+        $instanceid = $olddata['id'];
+        $contextid  = $this->converter->get_contextid(CONTEXT_BLOCK, $olddata['id']);
+        $configdata = unserialize(base64_decode($olddata['configdata']));
+
+        // get a fresh new file manager for this instance
+        $this->fileman = $this->converter->get_file_manager($contextid, 'block_html');
+
+        // convert course files embedded in the block content
+        $this->fileman->filearea = 'content';
+        $this->fileman->itemid   = 0;
+        $configdata->text = moodle1_converter::migrate_referenced_files($configdata->text, $this->fileman);
+        $configdata->format = FORMAT_HTML;
+
+        return base64_encode(serialize($configdata));
+    }
+
+    protected function write_inforef_xml($newdata, $data) {
+        $this->open_xml_writer("course/blocks/{$data['name']}_{$data['id']}/inforef.xml");
+        $this->xmlwriter->begin_tag('inforef');
+        $this->xmlwriter->begin_tag('fileref');
+        foreach ($this->fileman->get_fileids() as $fileid) {
+            $this->write_xml('file', array('id' => $fileid));
+        }
+        $this->xmlwriter->end_tag('fileref');
+        $this->xmlwriter->end_tag('inforef');
+        $this->close_xml_writer();
+    }
+}
index 70d6054..7fdea6b 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2013020800;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900;        // Requires this Moodle version
 $plugin->component = 'block_navigation'; // Full name of the plugin (used for diagnostics)
index 62f417e..d2631dd 100644 (file)
@@ -86,6 +86,8 @@ var NODETYPE = {
     SYSTEM : 1,
     /** @type int Course category = 10 */
     CATEGORY : 10,
+    /** @type int MYCATEGORY = 11 */
+    MYCATEGORY : 11,
     /** @type int Course = 20 */
     COURSE : 20,
     /** @type int Course section = 30 */
@@ -454,7 +456,8 @@ BRANCH.prototype = {
                         this.addChild(object.children[i]);
                     }
                 }
-                if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE) && coursecount >= M.block_navigation.courselimit) {
+                if ((this.get('type') == NODETYPE.CATEGORY || this.get('type') == NODETYPE.ROOTNODE || this.get('type') == NODETYPE.MYCATEGORY)
+                    && coursecount >= M.block_navigation.courselimit) {
                     this.addViewAllCoursesChild(this);
                 }
                 this.get('tree').toggleExpansion({target:this.node});
@@ -486,7 +489,8 @@ BRANCH.prototype = {
                     branch.addChild(children[i]);
                 }
             }
-            if (branch.get('type') == NODETYPE.CATEGORY && count >= M.block_navigation.courselimit) {
+            if ((branch.get('type') == NODETYPE.CATEGORY || branch.get('type') == NODETYPE.MYCATEGORY)
+                && count >= M.block_navigation.courselimit) {
                 this.addViewAllCoursesChild(branch);
             }
         }
index 846ab98..b62eefb 100644 (file)
@@ -113,7 +113,8 @@ class block_recent_activity extends block_base {
      * Returns list of recent changes in course structure
      *
      * It includes adding, editing or deleting of the resources or activities
-     * Excludes changes on labels, and also if activity was both added and deleted
+     * Excludes changes on modules without a view link (i.e. labels), and also
+     * if activity was both added and deleted
      *
      * @return array array of changes. Each element is an array containing attributes:
      *    'action' - one of: 'add mod', 'update mod', 'delete mod'
@@ -135,13 +136,6 @@ class block_recent_activity extends block_base {
             foreach ($logs as $key => $log) {
                 $info = explode(' ', $log->info);
 
-                // note: in most cases I replaced hardcoding of label with use of
-                // $cm->has_view() but it was not possible to do this here because
-                // we don't necessarily have the $cm for it
-                if ($info[0] == 'label') {     // Labels are ignored in recent activity
-                    continue;
-                }
-
                 if (count($info) != 2) {
                     debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
                     continue;
@@ -151,6 +145,11 @@ class block_recent_activity extends block_base {
                 $instanceid = $info[1];
 
                 if ($log->action == 'delete mod') {
+                    if (plugin_supports('mod', $modname, FEATURE_NO_VIEW_LINK, false)) {
+                        // we should better call cm_info::has_view() because it can be
+                        // dynamic. But there is no instance of cm_info now
+                        continue;
+                    }
                     // unfortunately we do not know if the mod was visible
                     if (!array_key_exists($log->info, $newgones)) {
                         $changelist[$log->info] = array('action' => $log->action,
@@ -168,7 +167,7 @@ class block_recent_activity extends block_base {
                         continue;
                     }
                     $cm = $modinfo->instances[$modname][$instanceid];
-                    if ($cm->uservisible && empty($changelist[$log->info])) {
+                    if ($cm->has_view() && $cm->uservisible && empty($changelist[$log->info])) {
                         $changelist[$log->info] = array('action' => $log->action, 'module' => $cm);
                     }
                 }
index 95da634..d698365 100644 (file)
@@ -1,34 +1,34 @@
-<?php\r
-\r
-/**\r
- * Provides support for the conversion of moodle1 backup to the moodle2 format\r
- *\r
- * @package    block_rss_client\r
- * @copyright  2012 Paul Nicholls\r
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\r
- */\r
-\r
-defined('MOODLE_INTERNAL') || die();\r
-\r
-/**\r
- * Block conversion handler for rss_client\r
- */\r
-class moodle1_block_rss_client_handler extends moodle1_block_handler {\r
-    public function process_block(array $data) {\r
-        parent::process_block($data);\r
-        $instanceid = $data['id'];\r
-        $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);\r
-\r
-        // Moodle 1.9 backups do not include sufficient data to restore feeds, so we need an empty shell rss_client.xml\r
-        // for the restore process to find\r
-        $this->open_xml_writer("course/blocks/{$data['name']}_{$instanceid}/rss_client.xml");\r
-        $this->xmlwriter->begin_tag('block', array('id' => $instanceid, 'contextid' => $contextid, 'blockname' => 'rss_client'));\r
-        $this->xmlwriter->begin_tag('rss_client', array('id' => $instanceid));\r
-        $this->xmlwriter->full_tag('feeds', '');\r
-        $this->xmlwriter->end_tag('rss_client');\r
-        $this->xmlwriter->end_tag('block');\r
-        $this->close_xml_writer();\r
-\r
-        return $data;\r
-    }\r
-}
\ No newline at end of file
+<?php
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package    block_rss_client
+ * @copyright  2012 Paul Nicholls
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Block conversion handler for rss_client
+ */
+class moodle1_block_rss_client_handler extends moodle1_block_handler {
+    public function process_block(array $data) {
+        parent::process_block($data);
+        $instanceid = $data['id'];
+        $contextid = $this->converter->get_contextid(CONTEXT_BLOCK, $data['id']);
+
+        // Moodle 1.9 backups do not include sufficient data to restore feeds, so we need an empty shell rss_client.xml
+        // for the restore process to find
+        $this->open_xml_writer("course/blocks/{$data['name']}_{$instanceid}/rss_client.xml");
+        $this->xmlwriter->begin_tag('block', array('id' => $instanceid, 'contextid' => $contextid, 'blockname' => 'rss_client'));
+        $this->xmlwriter->begin_tag('rss_client', array('id' => $instanceid));
+        $this->xmlwriter->full_tag('feeds', '');
+        $this->xmlwriter->end_tag('rss_client');
+        $this->xmlwriter->end_tag('block');
+        $this->close_xml_writer();
+
+        return $data;
+    }
+}
index 3563843..2ac37aa 100644 (file)
@@ -25,6 +25,6 @@
  */
 
 $string['enabledock'] = 'Allow the user to dock this block';
-$string['pluginname'] = 'Settings';
-$string['settings:addinstance'] = 'Add a new settings block';
-$string['settings:myaddinstance'] = 'Add a new settings block to the My Moodle page';
+$string['pluginname'] = 'Administration';
+$string['settings:addinstance'] = 'Add a new administration block';
+$string['settings:myaddinstance'] = 'Add a new administration block to the My Moodle page';
index 919fed1..42ab6e3 100644 (file)
@@ -87,8 +87,8 @@ class blog_edit_form extends moodleform {
                 }
 
                 if (has_capability('moodle/blog:associatecourse', $context)) {
-                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));\r
-                    $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));\r
+                    $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
+                    $mform->addElement('advcheckbox', 'courseassoc', get_string('associatewithcourse', 'blog', $a), null, null, array(0, $contextid));
                     $mform->setDefault('courseassoc', $contextid);
                 }
 
index fee8aea..232aa46 100644 (file)
@@ -71,6 +71,12 @@ class cache_config {
      */
     protected $configlocks = array();
 
+    /**
+     * The site identifier used when the cache config was last saved.
+     * @var string
+     */
+    protected $siteidentifier = null;
+
     /**
      * Please use cache_config::instance to get an instance of the cache config that is ready to be used.
      */
@@ -139,6 +145,12 @@ class cache_config {
         $this->configdefinitionmappings = array();
         $this->configlockmappings = array();
 
+        $siteidentifier = 'unknown';
+        if (array_key_exists('siteidentifier', $configuration)) {
+            $siteidentifier = $configuration['siteidentifier'];
+        }
+        $this->siteidentifier = $siteidentifier;
+
         // Filter the lock instances.
         $defaultlock = null;
         foreach ($configuration['locks'] as $conf) {
@@ -271,6 +283,14 @@ class cache_config {
         return true;
     }
 
+    /**
+     * Returns the site identifier used by the cache API.
+     * @return string
+     */
+    public function get_site_identifier() {
+        return $this->siteidentifier;
+    }
+
     /**
      * Includes the configuration file and makes sure it contains the expected bits.
      *
index e278bbb..8c5a16a 100644 (file)
@@ -723,7 +723,8 @@ class cache_definition {
      */
     public function generate_single_key_prefix() {
         if ($this->keyprefixsingle === null) {
-            $this->keyprefixsingle = $this->mode.'/'.$this->mode;
+            $this->keyprefixsingle = $this->mode.'/'.$this->component.'/'.$this->area;
+            $this->keyprefixsingle .= '/'.$this->get_cache_identifier();
             $identifiers = $this->get_identifiers();
             if ($identifiers) {
                 foreach ($identifiers as $key => $value) {
@@ -746,6 +747,7 @@ class cache_definition {
                 'mode' => $this->mode,
                 'component' => $this->component,
                 'area' => $this->area,
+                'siteidentifier' => $this->get_cache_identifier()
             );
             if (!empty($this->identifiers)) {
                 $identifiers = array();
@@ -785,4 +787,13 @@ class cache_definition {
     public function get_invalidation_events() {
         return $this->invalidationevents;
     }
+
+    /**
+     * Returns a cache identification string.
+     *
+     * @return string A string to be used as part of keys.
+     */
+    protected function get_cache_identifier() {
+        return cache_helper::get_site_identifier();
+    }
 }
\ No newline at end of file
index f3d520c..24c7c1e 100644 (file)
@@ -203,6 +203,7 @@ class cache_factory {
         }
         // Get the class. Note this is a late static binding so we need to use get_called_class.
         $definition = cache_definition::load_adhoc($mode, $component, $area, $options);
+        $config = $this->create_config_instance();
         $definition->set_identifiers($identifiers);
         $cache = $this->create_cache($definition, $identifiers);
         if ($definition->should_be_persistent()) {
index 4439a56..a8f3a26 100644 (file)
@@ -54,6 +54,13 @@ class cache_helper {
      */
     protected static $instance;
 
+    /**
+     * The site identifier used by the cache.
+     * Set the first time get_site_identifier is called.
+     * @var string
+     */
+    protected static $siteidentifier = null;
+
     /**
      * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
      *
@@ -489,11 +496,52 @@ class cache_helper {
      */
     public static function update_definitions($coreonly = false) {
         global $CFG;
-        // Include locallib
+        // Include locallib.
         require_once($CFG->dirroot.'/cache/locallib.php');
         // First update definitions
         cache_config_writer::update_definitions($coreonly);
         // Second reset anything we have already initialised to ensure we're all up to date.
         cache_factory::reset();
     }
-}
\ No newline at end of file
+
+    /**
+     * Update the site identifier stored by the cache API.
+     *
+     * @param string $siteidentifier
+     */
+    public static function update_site_identifier($siteidentifier) {
+        global $CFG;
+        // Include locallib.
+        require_once($CFG->dirroot.'/cache/locallib.php');
+        $factory = cache_factory::instance();
+        $factory->updating_started();
+        $config = $factory->create_config_instance(true);
+        $config->update_site_identifier($siteidentifier);
+        $factory->updating_finished();
+        cache_factory::reset();
+    }
+
+    /**
+     * Returns the site identifier.
+     *
+     * @return string
+     */
+    public static function get_site_identifier() {
+        if (is_null(self::$siteidentifier)) {
+            $factory = cache_factory::instance();
+            $config = $factory->create_config_instance();
+            self::$siteidentifier = $config->get_site_identifier();
+        }
+        return self::$siteidentifier;
+    }
+
+    /**
+     * Returns the site version.
+     *
+     * @return string
+     */
+    public static function get_site_version() {
+        global $CFG;
+        return (string)$CFG->version;
+    }
+}
index ce926a5..4d6d745 100644 (file)
@@ -119,6 +119,7 @@ class cache_config_writer extends cache_config {
      */
     protected function generate_configuration_array() {
         $configuration = array();
+        $configuration['siteidentifier'] = $this->siteidentifier;
         $configuration['stores'] = $this->configstores;
         $configuration['modemappings'] = $this->configmodemappings;
         $configuration['definitions'] = $this->configdefinitions;
@@ -524,6 +525,15 @@ class cache_config_writer extends cache_config {
         $this->config_save();
     }
 
+    /**
+     * Update the site identifier stored by the cache API.
+     *
+     * @param string $siteidentifier
+     */
+    public function update_site_identifier($siteidentifier) {
+        $this->siteidentifier = md5((string)$siteidentifier);
+        $this->config_save();
+    }
 }
 
 /**
@@ -1002,4 +1012,4 @@ abstract class cache_administration_helper extends cache_helper {
         }
         return $locks;
     }
-}
\ No newline at end of file
+}
index 98d9c5b..545986a 100644 (file)
@@ -553,6 +553,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
             'invalidationevents' => array(
                 'crazyevent'
             )
@@ -567,13 +569,16 @@ class cache_phpunit_tests extends advanced_testcase {
 
         // OK data added, data invalidated, and invalidation time has been set.
         // Now we need to manually add back the data and adjust the invalidation time.
-        $timefile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/a65/a65b1dc524cf6e03c1795197c84d5231eb229b86.cache';
+        $hash = md5(cache_store::MODE_APPLICATION.'/phpunit/eventinvalidationtest/'.$CFG->wwwroot.'phpunit');
+        $timefile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/las/lastinvalidation-$hash.cache";
+        // Make sure the file is correct.
+        $this->assertTrue(file_exists($timefile));
         $timecont = serialize(cache::now() - 60); // Back 60sec in the past to force it to re-invalidate.
         make_writable_directory(dirname($timefile));
         file_put_contents($timefile, $timecont);
         $this->assertTrue(file_exists($timefile));
 
-        $datafile = $CFG->dataroot.'/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/626/626e9c7a45febd98f064c2b383de8d9d4ebbde7b.cache';
+        $datafile = $CFG->dataroot."/cache/cachestore_file/default_application/phpunit_eventinvalidationtest/tes/testkey1-$hash.cache";
         $datacont = serialize("test data 1");
         make_writable_directory(dirname($datafile));
         file_put_contents($datafile, $datacont);
@@ -586,6 +591,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
         ));
         $cache = cache::make('phpunit', 'eventinvalidationtest');
         $this->assertEquals('test data 1', $cache->get('testkey1'));
@@ -597,6 +604,8 @@ class cache_phpunit_tests extends advanced_testcase {
             'mode' => cache_store::MODE_APPLICATION,
             'component' => 'phpunit',
             'area' => 'eventinvalidationtest',
+            'simplekeys' => true,
+            'simpledata' => true,
             'invalidationevents' => array(
                 'crazyevent'
             )
index d5e5297..471521b 100644 (file)
@@ -102,6 +102,16 @@ class cache_config_phpunittest extends cache_config_writer {
             'sort' => (int)$sort
         );
     }
+
+    /**
+     * Overrides the default site identifier used by the Cache API so that we can be sure of what it is.
+     *
+     * @return string
+     */
+    public function get_site_identifier() {
+        global $CFG;
+        return $CFG->wwwroot.'phpunit';
+    }
 }
 
 /**
index 91b99ca..c1fe37c 100644 (file)
@@ -22,6 +22,7 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 define('AJAX_SCRIPT', true);
+define('NO_DEBUG_DISPLAY', true);
 
 require_once('../config.php');
 require_once($CFG->dirroot . '/comment/lib.php');
@@ -35,6 +36,10 @@ if (empty($CFG->usecomments)) {
 
 list($context, $course, $cm) = get_context_info_array($contextid);
 
+if ( $contextid == SYSCONTEXTID ) {
+    $course = $SITE;
+}
+
 $PAGE->set_url('/comment/comment_ajax.php');
 
 // Allow anonymous user to view comments providing forcelogin now enabled
index c146119..9ca0a41 100644 (file)
@@ -526,10 +526,10 @@ class comment {
             $c->content     = $u->ccontent;
             $c->format      = $u->cformat;
             $c->timecreated = $u->ctimecreated;
+            $c->strftimeformat = get_string('strftimerecent', 'langconfig');
             $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
             $c->profileurl = $url->out(false);
             $c->fullname = fullname($u);
-            $c->time = userdate($c->timecreated, get_string('strftimerecent', 'langconfig'));
             $c->content = format_text($c->content, $c->format, $formatoptions);
             $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
 
@@ -628,12 +628,27 @@ class comment {
         $cmt_id = $DB->insert_record('comments', $newcmt);
         if (!empty($cmt_id)) {
             $newcmt->id = $cmt_id;
-            $newcmt->time = userdate($now, get_string('strftimerecent', 'langconfig'));
+            $newcmt->strftimeformat = get_string('strftimerecent', 'langconfig');
             $newcmt->fullname = fullname($USER);
             $url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
             $newcmt->profileurl = $url->out();
             $newcmt->content = format_text($newcmt->content, $format, array('overflowdiv'=>true));
             $newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
+
+            $commentlist = array($newcmt);
+
+            if (!empty($this->plugintype)) {
+                // Call the display callback to allow the plugin to format the newly added comment.
+                $commentlist = plugin_callback($this->plugintype,
+                                               $this->pluginname,
+                                               'comment',
+                                               'display',
+                                               array($commentlist, $this->comment_param),
+                                               $commentlist);
+                $newcmt = $commentlist[0];
+            }
+            $newcmt->time = userdate($newcmt->timecreated, $newcmt->strftimeformat);
+
             return $newcmt;
         } else {
             throw new comment_exception('dbupdatefailed');
@@ -793,7 +808,7 @@ class comment {
         $replacements[] = $cmt->avatar;
         $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
         $replacements[] = $cmt->content;
-        $replacements[] = userdate($cmt->timecreated, get_string('strftimerecent', 'langconfig'));
+        $replacements[] = userdate($cmt->timecreated, $cmt->strftimeformat);
 
         // use html template to format a single comment.
         return str_replace($patterns, $replacements, $this->template);
index 0cdcc5f..40f8577 100644 (file)
@@ -353,7 +353,8 @@ $CFG->admin = 'admin';
 //
 //     $CFG->themedir = '/location/of/extra/themes';
 //
-// It is possible to specify different cache and temp directories, use local fast filesystem.
+// It is possible to specify different cache and temp directories, use local fast filesystem
+// for normal web servers. Server clusters MUST use shared filesystem for cachedir!
 // The directories must not be accessible via web.
 //
 //     $CFG->tempdir = '/var/www/moodle/temp';
index ee8eb70..291583c 100644 (file)
@@ -549,7 +549,7 @@ M.course_dndupload = {
         var extension = '';
         var dotpos = file.name.lastIndexOf('.');
         if (dotpos != -1) {
-            extension = file.name.substr(dotpos+1, file.name.length);
+            extension = file.name.substr(dotpos+1, file.name.length).toLowerCase();
         }
 
         for (var i=0; i<filehandlers.length; i++) {
index 5b901b2..6bc2895 100644 (file)
@@ -1920,14 +1920,14 @@ function print_course_search($value="", $return=false, $format="plain") {
         $output  = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
         $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
         $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
-        $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="text" id="shortsearchbox" size="12" name="search" value="'.s($value).'" />';
         $output .= '<input type="submit" value="'.get_string('go').'" />';
         $output .= '</fieldset></form>';
     } else if ($format == 'navbar') {
         $output  = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
         $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
         $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
-        $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="text" id="navsearchbox" size="20" name="search" value="'.s($value).'" />';
         $output .= '<input type="submit" value="'.get_string('go').'" />';
         $output .= '</fieldset></form>';
     }
@@ -2531,7 +2531,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
     $actions = array();
 
     // AJAX edit title
-    if ($mod->modname !== 'label' && $hasmanageactivities &&
+    if ($mod->has_view() && $hasmanageactivities &&
                 (($mod->course == $COURSE->id && course_ajax_enabled($COURSE)) ||
                  ($mod->course == SITEID && course_ajax_enabled($SITE)))) {
         // we will not display link if we are on some other-course page (where we should not see this module anyway)
index 26827a7..177e922 100644 (file)
@@ -339,8 +339,8 @@ if (!isset($category)) {
     $table->head = array(
         get_string('categories'),
         get_string('courses'),
-        get_string('movecategoryto'),
         get_string('edit'),
+        get_string('movecategoryto'),
     );
     $table->colclasses = array(
         'leftalign name',
index e20cdc5..0f1e0ca 100644 (file)
@@ -83,9 +83,6 @@ class behat_course extends behat_base {
      */
     public function i_add_to_section($activity, $section) {
 
-        $activity = $this->fixStepArgument($activity);
-        $section = $this->fixStepArgument($section);
-
         // Clicks add activity or resource section link.
         $sectionxpath = "//*[@id='section-" . $section . "']/*/*/*/div[@class='section-modchooser']/span/a";
         $sectionnode = $this->find('xpath', $sectionxpath);
index a59351b..ef8ef1b 100644 (file)
@@ -297,22 +297,71 @@ class courselib_testcase extends advanced_testcase {
     }
 
     public function test_move_module_in_course() {
+        global $DB;
+
         $this->resetAfterTest(true);
         // Setup fixture
-        $course = $this->getDataGenerator()->create_course(array('numsections'=>5));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
         $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
 
         $cms = get_fast_modinfo($course)->get_cms();
         $cm = reset($cms);
 
-        course_create_sections_if_missing($course, 3);
-        $section3 = get_fast_modinfo($course)->get_section_info(3);
+        $newsection = get_fast_modinfo($course)->get_section_info(3);
+        $oldsectionid = $cm->section;
+
+        // Perform the move
+        moveto_module($cm, $newsection);
 
-        moveto_module($cm, $section3);
+        // reset of get_fast_modinfo is usually called the code calling moveto_module so call it here
+        get_fast_modinfo(0, 0, true);
+        $cms = get_fast_modinfo($course)->get_cms();
+        $cm = reset($cms);
 
+        // Check that the cached modinfo contains the correct section info
         $modinfo = get_fast_modinfo($course);
         $this->assertTrue(empty($modinfo->sections[0]));
         $this->assertFalse(empty($modinfo->sections[3]));
+
+        // Check that the old section's sequence no longer contains this ID
+        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
+        $oldsequences = explode(',', $newsection->sequence);
+        $this->assertFalse(in_array($cm->id, $oldsequences));
+
+        // Check that the new section's sequence now contains this ID
+        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
+        $newsequences = explode(',', $newsection->sequence);
+        $this->assertTrue(in_array($cm->id, $newsequences));
+
+        // Check that the section number has been changed in the cm
+        $this->assertEquals($newsection->id, $cm->section);
+
+
+        // Perform a second move as some issues were only seen on the second move
+        $newsection = get_fast_modinfo($course)->get_section_info(2);
+        $oldsectionid = $cm->section;
+        $result = moveto_module($cm, $newsection);
+        $this->assertTrue($result);
+
+        // reset of get_fast_modinfo is usually called the code calling moveto_module so call it here
+        get_fast_modinfo(0, 0, true);
+        $cms = get_fast_modinfo($course)->get_cms();
+        $cm = reset($cms);
+
+        // Check that the cached modinfo contains the correct section info
+        $modinfo = get_fast_modinfo($course);
+        $this->assertTrue(empty($modinfo->sections[0]));
+        $this->assertFalse(empty($modinfo->sections[2]));
+
+        // Check that the old section's sequence no longer contains this ID
+        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
+        $oldsequences = explode(',', $newsection->sequence);
+        $this->assertFalse(in_array($cm->id, $oldsequences));
+
+        // Check that the new section's sequence now contains this ID
+        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
+        $newsequences = explode(',', $newsection->sequence);
+        $this->assertTrue(in_array($cm->id, $newsequences));
     }
 
     public function test_module_visibility() {
index 4e6ad15..8560eef 100644 (file)
@@ -15,7 +15,6 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         GROUPSNONE : 'a.editing_groupsnone',
         GROUPSSEPARATE : 'a.editing_groupsseparate',
         GROUPSVISIBLE : 'a.editing_groupsvisible',
-        HASLABEL : 'label',
         HIDE : 'a.editing_hide',
         HIGHLIGHT : 'a.editing_highlight',
         INSTANCENAME : 'span.instancename',
@@ -62,7 +61,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
 
             var dimarea;
             var toggle_class;
-            if (this.is_label(element)) {
+            if (this.get_instance_name(element) == null) {
                 toggle_class = CSS.DIMMEDTEXT;
                 dimarea = element.all(CSS.MODINDENTDIV + ' > div').item(1);
             } else {
@@ -172,8 +171,19 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             Y.io(uri, config);
             return responsetext;
         },
-        is_label : function(target) {
-            return target.hasClass(CSS.HASLABEL);
+        /**
+         * Return the name of the activity instance
+         *
+         * If activity has no name (for example label) null is returned
+         *
+         * @param element The <li> element to determine a name for
+         * @return string|null Instance name
+         */
+        get_instance_name : function(target) {
+            if (target.one(CSS.INSTANCENAME)) {
+                return target.one(CSS.INSTANCENAME).get('firstChild').get('data');
+            }
+            return null;
         },
         /**
          * Return the module ID for the specified element
@@ -330,19 +340,16 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             // Get the element we're working on
             var element   = e.target.ancestor(CSS.ACTIVITYLI);
 
+            // Create confirm string (different if element has or does not have name)
             var confirmstring = '';
-            if (this.is_label(element)) {
-                // Labels are slightly different to other activities
-                var plugindata = {
-                    type : M.util.get_string('pluginname', 'label')
-                }
-                confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
-            } else {
-                var plugindata = {
-                    type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1]),
-                    name : element.one(CSS.INSTANCENAME).get('firstChild').get('data')
-                }
+            var plugindata = {
+                type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1])
+            }
+            if (this.get_instance_name(element) != null) {
+                plugindata.name = this.get_instance_name(element)
                 confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
+            } else {
+                confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
             }
 
             // Confirm element removal
index a2370d4..6741732 100644 (file)
@@ -118,7 +118,8 @@ class enrol_cohort_plugin extends enrol_plugin {
 
         if (has_capability('enrol/cohort:config', $context)) {
             $editlink = new moodle_url("/enrol/cohort/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'smallicon')));
         }
 
         return $icons;
diff --git a/enrol/flatfile/adminlib.php b/enrol/flatfile/adminlib.php
new file mode 100644 (file)
index 0000000..971a847
--- /dev/null
@@ -0,0 +1,57 @@
+<?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/>.
+
+/**
+ * Special flatfile settings.
+ *
+ * @package    enrol_flatfile
+ * @copyright  2013 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once("$CFG->libdir/adminlib.php");
+
+
+/**
+ * Setting class that stores only non-empty values.
+ */
+class enrol_flatfile_role_setting extends admin_setting_configtext {
+
+    public function __construct($role) {
+        parent::__construct('enrol_flatfile/map_'.$role->id, $role->localname, '', $role->shortname);
+    }
+
+    public function config_read($name) {
+        $value = parent::config_read($name);
+        if (is_null($value)) {
+            // In other settings NULL means we have to ask user for new value,
+            // here we just ignore missing role mappings.
+            $value = '';
+        }
+        return $value;
+    }
+
+    public function config_write($name, $value) {
+        if ($value === '') {
+            // We do not want empty values in config table,
+            // delete it instead.
+            $value = null;
+        }
+        return parent::config_write($name, $value);
+    }
+}
diff --git a/enrol/flatfile/db/install.php b/enrol/flatfile/db/install.php
new file mode 100644 (file)
index 0000000..93c2cda
--- /dev/null
@@ -0,0 +1,35 @@
+<?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/>.
+
+/**
+ * Flatfile enrolment plugin installation.
+ *
+ * @package    enrol_flatfile
+ * @copyright  2013 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+function xmldb_enrol_flatfile_install() {
+    global $CFG, $DB;
+
+    // Flatfile role mappings are empty by default now.
+    $roles = get_all_roles();
+    foreach ($roles as $role) {
+        set_config('map_'.$role->id, $role->shortname, 'enrol_flatfile');
+    }
+}
index d8bba47..379568e 100644 (file)
@@ -25,6 +25,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+require_once(__DIR__.'/adminlib.php');
+
 if ($ADMIN->fulltree) {
 
     //--- general settings -----------------------------------------------------------------------------------
@@ -61,8 +63,8 @@ if ($ADMIN->fulltree) {
 
         $roles = role_fix_names(get_all_roles());
 
-        foreach ($roles as $id => $role) {
-            $settings->add(new admin_setting_configtext('enrol_flatfile/map_'.$id, $role->localname, '', $role->shortname));
+        foreach ($roles as $role) {
+            $settings->add(new enrol_flatfile_role_setting($role));
         }
         unset($roles);
     }
index 401d11a..8198f4b 100644 (file)
@@ -386,13 +386,6 @@ function process_group_tag($tagcontents) {
                             $this->log_line('No ' . $imsname . ' description tag found for ' . $coursecode . ' coursecode, using ' . $coursecode . ' instead');
                             $course->{$courseattr} = $coursecode;
                         }
-
-                        if ($courseattr == 'summary') {
-                            $format = FORMAT_HTML;
-                        } else {
-                            $format = FORMAT_PLAIN;
-                        }
-                        $course->{$courseattr} = format_text($course->$courseattr, $format);
                     }
 
                     $course->idnumber = $coursecode;
index 13c6114..921bb30 100644 (file)
@@ -114,9 +114,9 @@ class imsenterprise_courses {
      * @return array Array of assignable values
      */
     function get_imsnames($courseattr) {
-\r
-        $values = $this->imsnames;\r
-        if ($courseattr == 'summary') {\r
+
+        $values = $this->imsnames;
+        if ($courseattr == 'summary') {
             $values = array_merge(array('ignore' => get_string('emptyattribute', 'enrol_imsenterprise')), $values);
         }
         return $values;
index 5e947b6..3f6a506 100644 (file)
@@ -318,11 +318,12 @@ class course_enrolment_manager {
      * @param array $params query parameters.
      * @param int $page which page number of the results to show.
      * @param int $perpage number of users per page.
+     * @param int $addedenrollment number of users added to enrollment.
      * @return array with two elememts:
      *      int total number of users matching the search.
      *      array of user objects returned by the query.
      */
-    protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage) {
+    protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage, $addedenrollment=0) {
         global $DB, $CFG;
 
         list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context());
@@ -330,7 +331,7 @@ class course_enrolment_manager {
 
         $totalusers = $DB->count_records_sql($countfields . $sql, $params);
         $availableusers = $DB->get_records_sql($fields . $sql . $order,
-                array_merge($params, $sortparams), $page*$perpage, $perpage);
+                array_merge($params, $sortparams), ($page*$perpage) - $addedenrollment, $perpage);
 
         return array('totalusers' => $totalusers, 'users' => $availableusers);
     }
@@ -344,9 +345,10 @@ class course_enrolment_manager {
      * @param bool $searchanywhere
      * @param int $page Defaults to 0
      * @param int $perpage Defaults to 25
+     * @param int $addedenrollment Defaults to 0
      * @return array Array(totalusers => int, users => array)
      */
-    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25) {
+    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25, $addedenrollment=0) {
         global $DB;
 
         list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere);
@@ -359,7 +361,7 @@ class course_enrolment_manager {
                       AND ue.id IS NULL";
         $params['enrolid'] = $enrolid;
 
-        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage);
+        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, $addedenrollment);
     }
 
     /**
index 093ee05..ea4acd8 100644 (file)
@@ -67,7 +67,9 @@ switch ($action) {
         $enrolid = required_param('enrolid', PARAM_INT);
         $search = optional_param('search', '', PARAM_RAW);
         $page = optional_param('page', 0, PARAM_INT);
-        $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page);
+        $addedenrollment = optional_param('enrolcount', 0, PARAM_INT);
+        $perpage = optional_param('perpage', 25, PARAM_INT);  //  This value is hard-coded to 25 in quickenrolment.js
+        $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page, $perpage, $addedenrollment);
         $extrafields = get_extra_user_fields($context);
         foreach ($outcome->response['users'] as &$user) {
             $user->picture = $OUTPUT->user_picture($user);
index 7ffba73..9dae107 100644 (file)
@@ -68,7 +68,9 @@ class enrol_manual_plugin extends enrol_plugin {
 
         $context = context_course::instance($instance->courseid, MUST_EXIST);
 
-        if (!has_capability('enrol/manual:manage', $context) or !has_capability('enrol/manual:enrol', $context) or !has_capability('enrol/manual:unenrol', $context)) {
+        if (!has_capability('enrol/manual:enrol', $context) and !has_capability('enrol/manual:unenrol', $context)) {
+            // Note: manage capability not used here because it is used for editing
+            // of existing enrolments which is not possible here.
             return NULL;
         }
 
@@ -111,13 +113,14 @@ class enrol_manual_plugin extends enrol_plugin {
 
         $icons = array();
 
-        if (has_capability('enrol/manual:manage', $context)) {
+        if (has_capability('enrol/manual:enrol', $context) or has_capability('enrol/manual:unenrol', $context)) {
             $managelink = new moodle_url("/enrol/manual/manage.php", array('enrolid'=>$instance->id));
             $icons[] = $OUTPUT->action_icon($managelink, new pix_icon('t/enrolusers', get_string('enrolusers', 'enrol_manual'), 'core', array('class'=>'iconsmall')));
         }
         if (has_capability('enrol/manual:config', $context)) {
             $editlink = new moodle_url("/enrol/manual/edit.php", array('courseid'=>$instance->courseid));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'iconsmall')));
         }
 
         return $icons;
index f7eab04..7d91e4b 100644 (file)
@@ -35,9 +35,17 @@ $course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_
 $context = context_course::instance($course->id, MUST_EXIST);
 
 require_login($course);
-require_capability('enrol/manual:enrol', $context);
-require_capability('enrol/manual:manage', $context);
-require_capability('enrol/manual:unenrol', $context);
+$canenrol = has_capability('enrol/manual:enrol', $context);
+$canunenrol = has_capability('enrol/manual:unenrol', $context);
+
+// Note: manage capability not used here because it is used for editing
+// of existing enrolments which is not possible here.
+
+if (!$canenrol and !$canunenrol) {
+    // No need to invent new error strings here...
+    require_capability('enrol/manual:enrol', $context);
+    require_capability('enrol/manual:unenrol', $context);
+}
 
 if ($roleid < 0) {
     $roleid = $instance->roleid;
@@ -95,7 +103,7 @@ if ($course->startdate > 0) {
 $basemenu[3] = get_string('today') . ' (' . userdate($today, $timeformat) . ')' ;
 
 // Process add and removes.
-if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
+if ($canenrol && optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
     $userstoassign = $potentialuserselector->get_selected_users();
     if (!empty($userstoassign)) {
         foreach($userstoassign as $adduser) {
@@ -126,7 +134,7 @@ if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
 }
 
 // Process incoming role unassignments.
-if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
+if ($canunenrol && optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
     $userstounassign = $currentuserselector->get_selected_users();
     if (!empty($userstounassign)) {
         foreach($userstounassign as $removeuser) {
@@ -145,6 +153,9 @@ if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
 echo $OUTPUT->header();
 echo $OUTPUT->heading($instancename);
 
+$addenabled = $canenrol ? '' : 'disabled="disabled"';
+$removeenabled = $canunenrol ? '' : 'disabled="disabled"';
+
 ?>
 <form id="assignform" method="post" action="<?php echo $PAGE->url ?>"><div>
   <input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
@@ -157,7 +168,7 @@ echo $OUTPUT->heading($instancename);
       </td>
       <td id="buttonscell">
           <div id="addcontrols">
-              <input name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
+              <input name="add" <?php echo $addenabled; ?> id="add" type="submit" value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>" title="<?php print_string('add'); ?>" /><br />
 
               <div class="enroloptions">
 
@@ -174,7 +185,7 @@ echo $OUTPUT->heading($instancename);
           </div>
 
           <div id="removecontrols">
-              <input name="remove" id="remove" type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
+              <input name="remove" id="remove" <?php echo $removeenabled; ?> type="submit" value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>" title="<?php print_string('remove'); ?>" />
           </div>
       </td>
       <td id="potentialcell">
index aad6e62..8362185 100644 (file)
@@ -23,7 +23,9 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
         DEFAULTDURATION : 'defaultDuration',
         ASSIGNABLEROLES : 'assignableRoles',
         DISABLEGRADEHISTORY : 'disableGradeHistory',
-        RECOVERGRADESDEFAULT : 'recoverGradesDefault'
+        RECOVERGRADESDEFAULT : 'recoverGradesDefault',
+        ENROLCOUNT : 'enrolCount',
+        PERPAGE : 'perPage'
     };
     /** CSS classes for nodes in structure **/
     var CSS = {
@@ -309,6 +311,9 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             params['action'] = 'searchusers';
             params['search'] = this.get(UEP.SEARCH).get('value');
             params['page'] = this.get(UEP.PAGE);
+            params['enrolcount'] = this.get(UEP.ENROLCOUNT);
+            params['perpage'] = this.get(UEP.PERPAGE);
+
             if (this.get(UEP.MULTIPLE)) {
                 alert('oh no there are multiple');
             } else {
@@ -376,7 +381,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
                     .append(create('<div class="'+CSS.TOTALUSERS+'">'+usersstr+'</div>'))
                     .append(users);
-                if (result.response.totalusers > (this.get(UEP.PAGE)+1)*25) {
+                if (result.response.totalusers > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
                     var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.str.enrol.ajaxnext25+'</a></div>');
                     fetchmore.on('click', this.search, this, true);
                     content.append(fetchmore)
@@ -384,7 +389,7 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                 this.setContent(content);
                 Y.delegate("click", this.enrolUser, users, '.'+CSS.USER+' .'+CSS.ENROL, this, args);
             } else {
-                if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*25) {
+                if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
                     this.get(UEP.BASE).one('.'+CSS.MORERESULTS).remove();
                 }
             }
@@ -420,6 +425,8 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
                                 args.userNode.addClass(CSS.ENROLLED);
                                 args.userNode.one('.'+CSS.ENROL).remove();
                                 this.set(UEP.REQUIREREFRESH, true);
+                                var countenrol = this.get(UEP.ENROLCOUNT)+1;
+                                this.set(UEP.ENROLCOUNT, countenrol);
                             }
                         } catch (e) {
                             new M.core.exception(e);
@@ -532,6 +539,14 @@ YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
             },
             recoverGradesDefault : {
                 value : ''
+            },
+            enrolCount : {
+                value : 0,
+                validator : Y.Lang.isNumber
+            },
+            perPage : {
+                value: 25,
+                Validator: Y.Lang.isNumber
             }
         }
     });
index ca3cd74..b79dbe1 100644 (file)
@@ -118,7 +118,8 @@ class enrol_paypal_plugin extends enrol_plugin {
 
         if (has_capability('enrol/paypal:config', $context)) {
             $editlink = new moodle_url("/enrol/paypal/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                    array('class' => 'smallicon')));
         }
 
         return $icons;
index b624d92..31679c7 100644 (file)
@@ -160,7 +160,8 @@ class enrol_self_plugin extends enrol_plugin {
 
         if (has_capability('enrol/self:config', $context)) {
             $editlink = new moodle_url("/enrol/self/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
-            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('i/edit', get_string('edit'), 'core', array('class'=>'icon')));
+            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
+                array('class' => 'smallicon')));
         }
 
         return $icons;
index bef80bc..dc2b636 100644 (file)
@@ -36,8 +36,6 @@ class filter_activitynames extends moodle_text_filter {
     static $cachedcourseid;
 
     function filter($text, array $options = array()) {
-        global $CFG, $COURSE, $DB;
-
         if (!$courseid = get_courseid_from_context($this->context)) {
             return $text;
         }
@@ -53,35 +51,25 @@ class filter_activitynames extends moodle_text_filter {
         if (is_null(self::$activitylist)) {
             self::$activitylist = array();
 
-            if ($COURSE->id == $courseid) {
-                $course = $COURSE;
-            } else {
-                $course = $DB->get_record("course", array("id"=>$courseid));
-            }
-
-            if (!isset($course->modinfo)) {
-                return $text;
-            }
-
-        /// Casting $course->modinfo to string prevents one notice when the field is null
-            $modinfo = unserialize((string)$course->modinfo);
-
-            if (!empty($modinfo)) {
-
+            $modinfo = get_fast_modinfo($courseid);
+            if (!empty($modinfo->cms)) {
                 self::$activitylist = array();      /// We will store all the activities here
 
                 //Sort modinfo by name length
-                usort($modinfo, 'filter_activitynames_comparemodulenamesbylength');
+                $sortedactivities = fullclone($modinfo->cms);
+                usort($sortedactivities, 'filter_activitynames_comparemodulenamesbylength');
 
-                foreach ($modinfo as $activity) {
+                foreach ($sortedactivities as $cm) {
                     //Exclude labels, hidden activities and activities for group members only
-                    if ($activity->mod != "label" and $activity->visible and empty($activity->groupmembersonly)) {
-                        $title = s(trim(strip_tags($activity->name)));
-                        $currentname = trim($activity->name);
+                    if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
+                        $title = s(trim(strip_tags($cm->name)));
+                        $currentname = trim($cm->name);
                         $entitisedname  = s($currentname);
                         /// Avoid empty or unlinkable activity names
                         if (!empty($title)) {
-                            $href_tag_begin = "<a class=\"autolink\" title=\"$title\" href=\"$CFG->wwwroot/mod/$activity->mod/view.php?id=$activity->cm\">";
+                            $href_tag_begin = html_writer::start_tag('a',
+                                    array('class' => 'autolink', 'title' => $title,
+                                        'href' => $cm->get_url()));
                             self::$activitylist[] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
                             if ($currentname != $entitisedname) { /// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545
                                 self::$activitylist[] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
index 0ee92ba..54af548 100644 (file)
@@ -323,9 +323,9 @@ M.gradereport_grader.classes.ajax = function(report, cfg) {
                 this.existingfields[userid][itemid] = new M.gradereport_grader.classes.existingfield(this, userid, itemid);
             }
         }
-        // Hide the Update button
+        // Disable the Update button as we're saving using ajax.
         submitbutton = this.report.Y.one('#gradersubmit');
-        submitbutton.setStyle('visibility', 'hidden');
+        submitbutton.set('disabled', true);
     }
 };
 /**
index 5963c6e..48d656f 100644 (file)
@@ -760,6 +760,7 @@ class core_group_external extends external_api {
             array(
                 'groupingids' => new external_multiple_structure(new external_value(PARAM_INT, 'grouping ID')
                         , 'List of grouping id. A grouping id is an integer.'),
+                'returngroups' => new external_value(PARAM_BOOL, 'return associated groups', VALUE_DEFAULT, 0)
             )
         );
     }
@@ -768,15 +769,18 @@ class core_group_external extends external_api {
      * Get groupings definition specified by ids
      *
      * @param array $groupingids arrays of grouping ids
+     * @param boolean $returngroups return the associated groups if true. The default is false.
      * @return array of grouping objects (id, courseid, name)
      * @since Moodle 2.3
      */
-    public static function get_groupings($groupingids) {
-        global $CFG;
+    public static function get_groupings($groupingids, $returngroups = false) {
+        global $CFG, $DB;
         require_once("$CFG->dirroot/group/lib.php");
         require_once("$CFG->libdir/filelib.php");
 
-        $params = self::validate_parameters(self::get_groupings_parameters(), array('groupingids'=>$groupingids));
+        $params = self::validate_parameters(self::get_groupings_parameters(),
+                                            array('groupingids' => $groupingids,
+                                                  'returngroups' => $returngroups));
 
         $groupings = array();
         foreach ($params['groupingids'] as $groupingid) {
@@ -799,7 +803,30 @@ class core_group_external extends external_api {
                 external_format_text($grouping->description, $grouping->descriptionformat,
                         $context->id, 'grouping', 'description', $grouping->id);
 
-            $groupings[] = (array)$grouping;
+            $groupingarray = (array)$grouping;
+
+            if ($params['returngroups']) {
+                $grouprecords = $DB->get_records_sql("SELECT * FROM {groups} g INNER JOIN {groupings_groups} gg ".
+                                               "ON g.id = gg.groupid WHERE gg.groupingid = ? ".
+                                               "ORDER BY groupid", array($groupingid));
+                if ($grouprecords) {
+                    $groups = array();
+                    foreach ($grouprecords as $grouprecord) {
+                        list($grouprecord->description, $grouprecord->descriptionformat) =
+                        external_format_text($grouprecord->description, $grouprecord->descriptionformat,
+                        $context->id, 'group', 'description', $grouprecord->groupid);
+                        $groups[] = array('id' => $grouprecord->groupid,
+                                          'name' => $grouprecord->name,
+                                          'description' => $grouprecord->description,
+                                          'descriptionformat' => $grouprecord->descriptionformat,
+                                          'enrolmentkey' => $grouprecord->enrolmentkey,
+                                          'courseid' => $grouprecord->courseid
+                                          );
+                    }
+                    $groupingarray['groups'] = $groups;
+                }
+            }
+            $groupings[] = $groupingarray;
         }
 
         return $groupings;
@@ -819,7 +846,19 @@ class core_group_external extends external_api {
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description' => new external_value(PARAM_RAW, 'grouping description text'),
-                    'descriptionformat' => new external_format_value('description')
+                    'descriptionformat' => new external_format_value('description'),
+                    'groups' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'id' => new external_value(PARAM_INT, 'group record id'),
+                                'courseid' => new external_value(PARAM_INT, 'id of course'),
+                                'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
+                                'description' => new external_value(PARAM_RAW, 'group description text'),
+                                'descriptionformat' => new external_format_value('description'),
+                                'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase')
+                            )
+                        ),
+                    'optional groups', VALUE_OPTIONAL)
                 )
             )
         );
index 04800d2..ec8b094 100644 (file)
@@ -83,17 +83,20 @@ if ($groupings = $DB->get_records('groupings', array('courseid'=>$course->id), '
         }
         $line[2] = $DB->count_records('course_modules', array('course'=>$course->id, 'groupingid'=>$grouping->id));
 
-        $buttons  = "<a title=\"$stredit\" href=\"grouping.php?id=$grouping->id\"><img".
-                    " src=\"" . $OUTPUT->pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"$stredit\" /></a> ";
+        $url = new moodle_url('/group/grouping.php', array('id' => $grouping->id));
+        $buttons  = html_writer::link($url, $OUTPUT->pix_icon('t/edit', $stredit, 'core',
+                array('class' => 'iconsmall')), array('title' => $stredit));
         if (empty($grouping->idnumber) || $canchangeidnumber) {
-            // It's only possible to delete groups without an idnumber unless the user has the changeidnumber capability
-            $buttons .= "<a title=\"$strdelete\" href=\"grouping.php?id=$grouping->id&amp;delete=1\"><img".
-                        " src=\"" . $OUTPUT->pix_url('t/delete') . "\" class=\"iconsmall\" alt=\"$strdelete\" /></a> ";
+            // It's only possible to delete groups without an idnumber unless the user has the changeidnumber capability.
+            $url = new moodle_url('/group/grouping.php', array('id' => $grouping->id, 'delete' => 1));
+            $buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/delete', $strdelete, 'core',
+                    array('class' => 'iconsmall')), array('title' => $strdelete));
         } else {
             $buttons .= $OUTPUT->spacer();
         }
-        $buttons .= "<a title=\"$strmanagegrping\" href=\"assign.php?id=$grouping->id\"><img".
-                    " src=\"" . $OUTPUT->pix_url('i/group') . "\" class=\"icon\" alt=\"$strmanagegrping\" /></a> ";
+        $url = new moodle_url('/group/assign.php', array('id' => $grouping->id));
+        $buttons .= html_writer::link($url, $OUTPUT->pix_icon('t/groups', $strmanagegrping, 'core',
+                array('class' => 'iconsmall')), array('title' => $strmanagegrping));
 
         $line[3] = $buttons;
         $data[] = $line;
index 996d3c6..8048d18 100644 (file)
@@ -30,6 +30,7 @@ global $CFG;
 
 require_once($CFG->dirroot . '/webservice/tests/helpers.php');
 require_once($CFG->dirroot . '/group/externallib.php');
+require_once($CFG->dirroot . '/group/lib.php');
 
 class core_group_external_testcase extends externallib_advanced_testcase {
 
@@ -207,4 +208,81 @@ class core_group_external_testcase extends externallib_advanced_testcase {
         $this->setExpectedException('required_capability_exception');
         $froups = core_group_external::delete_groups(array($group3->id));
     }
+
+    /**
+     * Test get_groupings
+     */
+    public function test_get_groupings() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $course = self::getDataGenerator()->create_course();
+
+        $groupingdata = array();
+        $groupingdata['courseid'] = $course->id;
+        $groupingdata['name'] = 'Grouping Test';
+        $groupingdata['description'] = 'Grouping Test description';
+        $groupingdata['descriptionformat'] = FORMAT_MOODLE;
+
+        $grouping = self::getDataGenerator()->create_grouping($groupingdata);
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/course:managegroups', $context->id);
+        $this->assignUserCapability('moodle/course:view', $context->id, $roleid);
+
+        // Call the external function without specifying the optional parameter.
+        $groupings = core_group_external::get_groupings(array($grouping->id));
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $groupings = external_api::clean_returnvalue(core_group_external::get_groupings_returns(), $groupings);
+
+        $this->assertEquals(1, count($groupings));
+
+        $group1data = array();
+        $group1data['courseid'] = $course->id;
+        $group1data['name'] = 'Group Test 1';
+        $group1data['description'] = 'Group Test 1 description';
+        $group1data['descriptionformat'] = FORMAT_MOODLE;
+        $group2data = array();
+        $group2data['courseid'] = $course->id;
+        $group2data['name'] = 'Group Test 2';
+        $group2data['description'] = 'Group Test 2 description';
+        $group2data['descriptionformat'] = FORMAT_MOODLE;
+
+        $group1 = self::getDataGenerator()->create_group($group1data);
+        $group2 = self::getDataGenerator()->create_group($group2data);
+
+        groups_assign_grouping($grouping->id, $group1->id);
+        groups_assign_grouping($grouping->id, $group2->id);
+
+        // Call the external function specifying that groups are returned.
+        $groupings = core_group_external::get_groupings(array($grouping->id), true);
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $groupings = external_api::clean_returnvalue(core_group_external::get_groupings_returns(), $groupings);
+        $this->assertEquals(1, count($groupings));
+        $this->assertEquals(2, count($groupings[0]['groups']));
+        foreach ($groupings[0]['groups'] as $group) {
+            $dbgroup = $DB->get_record('groups', array('id' => $group['id']), '*', MUST_EXIST);
+            $dbgroupinggroups = $DB->get_record('groupings_groups',
+                                                array('groupingid' => $groupings[0]['id'],
+                                                      'groupid' => $group['id']),
+                                                '*', MUST_EXIST);
+            switch ($dbgroup->name) {
+                case $group1->name:
+                    $groupdescription = $group1->description;
+                    $groupcourseid = $group1->courseid;
+                    break;
+                case $group2->name:
+                    $groupdescription = $group2->description;
+                    $groupcourseid = $group2->courseid;
+                    break;
+                default:
+                    throw new moodle_exception('unknowgroupname');
+                    break;
+            }
+            $this->assertEquals($dbgroup->description, $groupdescription);
+            $this->assertEquals($dbgroup->courseid, $groupcourseid);
+        }
+    }
 }
index 4996af6..53f68e2 100644 (file)
@@ -1035,7 +1035,7 @@ $string['updateavailable_version'] = 'Version {$a}';
 $string['updateavailableinstall'] = 'Install this update';
 $string['updateavailablenot'] = 'Your Moodle code is up-to-date!';
 $string['updatenotifications'] = 'Update notifications';
-$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Settings block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
+$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Administration block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
 $string['updatenotificationsubject'] = 'Moodle updates are available ({$a->siteurl})';
 $string['updateautocheck'] = 'Automatically check for available updates';
 $string['updateautocheck_desc'] = 'If enabled, your site will automatically check for available updates for both Moodle code and all additional plugins. If there is a new update available, a notification will be sent to site admins.';
index b4ef21a..190e57d 100644 (file)
@@ -30,7 +30,6 @@ $string['AG'] = 'Antigua And Barbuda';
 $string['AI'] = 'Anguilla';
 $string['AL'] = 'Albania';
 $string['AM'] = 'Armenia';
-$string['AN'] = 'Netherlands Antilles';
 $string['AO'] = 'Angola';
 $string['AQ'] = 'Antarctica';
 $string['AR'] = 'Argentina';
@@ -52,7 +51,8 @@ $string['BJ'] = 'Benin';
 $string['BL'] = 'Saint Barthélemy';
 $string['BM'] = 'Bermuda';
 $string['BN'] = 'Brunei Darussalam';
-$string['BO'] = 'Bolivia';
+$string['BO'] = 'Bolivia, Plurinational State Of';
+$string['BQ'] = 'Bonaire, Sint Eustatius And Saba';
 $string['BR'] = 'Brazil';
 $string['BS'] = 'Bahamas';
 $string['BT'] = 'Bhutan';
@@ -65,6 +65,7 @@ $string['CC'] = 'Cocos (Keeling) Islands';
 $string['CD'] = 'Congo, The Democratic Republic Of The';
 $string['CF'] = 'Central African Republic';
 $string['CG'] = 'Congo';
+$string['CH'] = 'Switzerland';
 $string['CI'] = 'Côte D\'Ivoire';
 $string['CK'] = 'Cook Islands';
 $string['CL'] = 'Chile';
@@ -74,6 +75,7 @@ $string['CO'] = 'Colombia';
 $string['CR'] = 'Costa Rica';
 $string['CU'] = 'Cuba';
 $string['CV'] = 'Cape Verde';
+$string['CW'] = 'Curaçao';
 $string['CX'] = 'Christmas Island';
 $string['CY'] = 'Cyprus';
 $string['CZ'] = 'Czech Republic';
@@ -121,7 +123,6 @@ $string['HN'] = 'Honduras';
 $string['HR'] = 'Croatia';
 $string['HT'] = 'Haiti';
 $string['HU'] = 'Hungary';
-$string['CH'] = 'Switzerland';
 $string['ID'] = 'Indonesia';
 $string['IE'] = 'Ireland';
 $string['IL'] = 'Israel';
@@ -157,12 +158,12 @@ $string['LS'] = 'Lesotho';
 $string['LT'] = 'Lithuania';
 $string['LU'] = 'Luxembourg';
 $string['LV'] = 'Latvia';
-$string['LY'] = 'Libyan Arab Jamahiriya';
+$string['LY'] = 'Libya';
 $string['MA'] = 'Morocco';
 $string['MC'] = 'Monaco';
 $string['MD'] = 'Moldova, Republic Of';
 $string['ME'] = 'Montenegro';
-$string['MF'] = 'Saint Martin';
+$string['MF'] = 'Saint Martin (French Part)';
 $string['MG'] = 'Madagascar';
 $string['MH'] = 'Marshall Islands';
 $string['MK'] = 'Macedonia, The Former Yugoslav Republic Of';
@@ -204,7 +205,7 @@ $string['PL'] = 'Poland';
 $string['PM'] = 'Saint Pierre And Miquelon';
 $string['PN'] = 'Pitcairn';
 $string['PR'] = 'Puerto Rico';
-$string['PS'] = 'Palestinian Territory, Occupied';
+$string['PS'] = 'Palestine, State Of';
 $string['PT'] = 'Portugal';
 $string['PW'] = 'Palau';
 $string['PY'] = 'Paraguay';
@@ -220,7 +221,7 @@ $string['SC'] = 'Seychelles';
 $string['SD'] = 'Sudan';
 $string['SE'] = 'Sweden';
 $string['SG'] = 'Singapore';
-$string['SH'] = 'Saint Helena';
+$string['SH'] = 'Saint Helena, Ascension And Tristan Da Cunha';
 $string['SI'] = 'Slovenia';
 $string['SJ'] = 'Svalbard And Jan Mayen';
 $string['SK'] = 'Slovakia';
@@ -229,8 +230,10 @@ $string['SM'] = 'San Marino';
 $string['SN'] = 'Senegal';
 $string['SO'] = 'Somalia';
 $string['SR'] = 'Suriname';
+$string['SS'] = 'South Sudan';
 $string['ST'] = 'Sao Tome And Principe';
 $string['SV'] = 'El Salvador';
+$string['SX'] = 'Sint Maarten (Dutch Part)';
 $string['SY'] = 'Syrian Arab Republic';
 $string['SZ'] = 'Swaziland';
 $string['TC'] = 'Turks And Caicos Islands';
@@ -257,7 +260,7 @@ $string['UY'] = 'Uruguay';
 $string['UZ'] = 'Uzbekistan';
 $string['VA'] = 'Holy See (Vatican City State)';
 $string['VC'] = 'Saint Vincent And The Grenadines';
-$string['VE'] = 'Venezuela';
+$string['VE'] = 'Venezuela, Bolivarian Republic Of';
 $string['VG'] = 'Virgin Islands, British';
 $string['VI'] = 'Virgin Islands, U.S.';
 $string['VN'] = 'Viet Nam';
index fed4bbc..3be622d 100644 (file)
@@ -31,7 +31,7 @@ $string['activemethodinfonone'] = 'There is no advanced grading method selected
 $string['changeactivemethod'] = 'Change active grading method to';
 $string['clicktoclose'] = 'click to close';
 $string['exc_gradingformelement'] = 'Unable to instantiate grading form element';
-$string['formnotavailable'] = 'Advanced grading method was selected to use but the grading form is not available yet. You may need to define it first via a link in the Settings block.';
+$string['formnotavailable'] = 'Advanced grading method was selected to use but the grading form is not available yet. You may need to define it first via a link in the Administration block.';
 $string['gradingformunavailable'] = 'Please note: the advanced grading form is not ready at the moment. Simple grading method will be used until the form has a valid status.';
 $string['gradingmanagement'] = 'Advanced grading';
 $string['gradingmanagementtitle'] = 'Advanced grading: {$a->component} ({$a->area})';
index 4d4dd8c..ae31942 100644 (file)
@@ -29,6 +29,7 @@ $string['actions'] = 'Actions';
 $string['availability'] = 'Availability';
 $string['checkforupdates'] = 'Check for available updates';
 $string['checkforupdateslast'] = 'Last check done on {$a}';
+$string['detectedmisplacedplugin'] = 'Plugin "{$a->component}" is installed in incorrect location "{$a->current}", expected location is "{$a->expected}"';
 $string['displayname'] = 'Plugin name';
 $string['err_response_curl'] = 'Unable to fetch available updates data - unexpected cURL error.';
 $string['err_response_format_version'] = 'Unexpected version of the response format. Please try to re-check for available updates.';
index 3ab441e..79cc8d2 100644 (file)
@@ -54,4 +54,4 @@ $string['ratinginvalid'] = 'Rating is invalid';
 $string['ratingtime'] = 'Restrict ratings to items with dates in this range:';
 $string['ratings'] = 'Ratings';
 $string['rolewarning'] = 'Roles with permission to rate';
-$string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the settings block.';
+$string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the administration block.';
index 41505e0..d703a77 100644 (file)
@@ -51,21 +51,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
      */
     const TIMEOUT = 6;
 
-    /**
-     * Returns fixed step argument (with \\" replaced back to ").
-     *
-     * \\ is the chars combination to add when you
-     * want to escape the " character that is used as var
-     * delimiter.
-     *
-     * @see Behat\MinkExtension\Context\MinkContext
-     * @param string $argument
-     * @return string
-     */
-    protected function fixStepArgument($argument) {
-        return str_replace('\\"', '"', $argument);
-    }
-
     /**
      * Locates url, based on provided path.
      * Override to provide custom routing mechanism.
index c2bf778..04e0b3c 100644 (file)
@@ -49,10 +49,21 @@ class behat_form_editor extends behat_form_field {
      */
     public function set_value($value) {
 
-        // Set the value to the iframe and save it to the textarea.
-        $editorid = $this->field->getAttribute('id');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").setContent("' . $value . '");');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        // If tinyMCE var exists means that we are using that editor.
+        if ($this->is_editor_available()) {
+
+            // Set the value to the iframe and save it to the textarea.
+            $editorid = $this->field->getAttribute('id');
+
+            $this->session->executeScript('
+                tinyMCE.get("'.$editorid.'").setContent("' . $value . '");
+                tinyMCE.get("'.$editorid.'").save();
+            ');
+
+        } else {
+            // Set the value to a textarea otherwise.
+            parent::set_value($value);
+        }
     }
 
     /**
@@ -62,12 +73,36 @@ class behat_form_editor extends behat_form_field {
      */
     public function get_value() {
 
-        // Save the current iframe value in case default value has been edited.
-        $editorid = $this->field->getAttribute('id');
-        $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        // If tinyMCE var exists means that we are using that editor.
+        if ($this->is_editor_available()) {
+
+            // Save the current iframe value in case default value has been edited.
+            $editorid = $this->field->getAttribute('id');
+            $this->session->executeScript('tinyMCE.get("'.$editorid.'").save();');
+        }
 
         return $this->field->getValue();
     }
 
+    /**
+     * Returns if the HTML editor is available.
+     *
+     * The editor availability depends on the driver running the tests; Goutte
+     * can not execute Javascript, also some Moodle settings disables the HTML
+     * editor.
+     *
+     * @return bool
+     */
+    protected function is_editor_available() {
+
+        // Non-JS drivers throws exceptions when running JS.
+        try {
+            $available = $this->session->evaluateScript('return (typeof tinyMCE != "undefined")');
+        } catch (Exception $e) {
+            $available = false;
+        }
+
+        return $available;
+    }
 }
 
index b246000..0d4a626 100644 (file)
@@ -1747,7 +1747,13 @@ class css_rule {
         $css = $this->out();
         $errors = array();
         foreach ($this->styles as $style) {
-            if ($style->has_error()) {
+            if (is_array($style)) {
+                foreach ($style as $s) {
+                    if ($style instanceof css_style && $style->has_error()) {
+                        $errors[] = "  * ".$style->get_last_error();
+                    }
+                }
+            } else if ($style instanceof css_style && $style->has_error()) {
                 $errors[] = "  * ".$style->get_last_error();
             }
         }
index a2a8d93..bc291e7 100644 (file)
@@ -273,6 +273,15 @@ $functions = array(
         'capabilities'=> 'moodle/user:create',
     ),
 
+    'core_user_get_users' => array(
+        'classname'   => 'core_user_external',
+        'methodname'  => 'get_users',
+        'classpath'   => 'user/externallib.php',
+        'description' => 'search for users matching the parameters',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/user:viewdetails, moodle/user:viewhiddendetails, moodle/course:useremail, moodle/user:update',
+    ),
+
     'moodle_user_get_users_by_id' => array(
         'classname'   => 'core_user_external',
         'methodname'  => 'get_users_by_id',
@@ -664,6 +673,33 @@ $functions = array(
         'capabilities'=> 'moodle/notes:manage',
     ),
 
+    'core_notes_delete_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'delete_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Delete notes',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/notes:manage',
+    ),
+
+    'core_notes_get_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'get_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Get notes',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/notes:view',
+    ),
+
+    'core_notes_update_notes' => array(
+        'classname'   => 'core_notes_external',
+        'methodname'  => 'update_notes',
+        'classpath'   => 'notes/externallib.php',
+        'description' => 'Update notes',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/notes:manage',
+    ),
+
     // === webservice related functions ===
 
     'moodle_webservice_get_siteinfo' => array(
index b563121..201a7a1 100644 (file)
@@ -1577,5 +1577,88 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013021100.01);
     }
 
+    if ($oldversion < 2013021800.00) {
+        // Add the site identifier to the cache config's file.
+        $siteidentifier = $DB->get_field('config', 'value', array('name' => 'siteidentifier'));
+        cache_helper::update_site_identifier($siteidentifier);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013021800.00);
+    }
+
+    if ($oldversion < 2013021801.00) {
+        // Fixing possible wrong MIME types for SMART Notebook files.
+        $extensions = array('%.gallery', '%.galleryitem', '%.gallerycollection', '%.nbk', '%.notebook', '%.xbk');
+        $select = $DB->sql_like('filename', '?', false);
+        foreach ($extensions as $extension) {
+            $DB->set_field_select(
+                'files',
+                'mimetype',
+                'application/x-smarttech-notebook',
+                $select,
+                array($extension)
+            );
+        }
+        upgrade_main_savepoint(true, 2013021801.00);
+    }
+
+    if ($oldversion < 2013021801.01) {
+        // Retrieve the list of course_sections as a recordset to save memory
+        $coursesections = $DB->get_recordset('course_sections', null, 'course, id', 'id, course, sequence');
+        foreach ($coursesections as $coursesection) {
+            // Retrieve all of the actual modules in this course and section combination to reduce DB calls
+            $actualsectionmodules = $DB->get_records('course_modules',
+                    array('course' => $coursesection->course, 'section' => $coursesection->id), '', 'id, section');
+
+            // Break out the current sequence so that we can compare it
+            $currentsequence = explode(',', $coursesection->sequence);
+            $newsequence = array();
+
+            // Check each of the modules in the current sequence
+            foreach ($currentsequence as $module) {
+                if (isset($actualsectionmodules[$module])) {
+                    $newsequence[] = $module;
+                    // We unset the actualsectionmodules so that we don't get duplicates and that we can add orphaned
+                    // modules later
+                    unset($actualsectionmodules[$module]);
+                }
+            }
+
+            // Append any modules which have somehow been orphaned
+            foreach ($actualsectionmodules as $module) {
+                $newsequence[] = $module->id;
+            }
+
+            // Piece it all back together
+            $sequence = implode(',', $newsequence);
+
+            // Only update if there have been changes
+            if ($sequence !== $coursesection->sequence) {
+                $coursesection->sequence = $sequence;
+                $DB->update_record('course_sections', $coursesection);
+
+                // And clear the sectioncache and modinfo cache - they'll be regenerated on next use
+                $course = new stdClass();
+                $course->id = $coursesection->course;
+                $course->sectioncache = null;
+                $course->modinfo = null;
+                $DB->update_record('course', $course);
+            }
+        }
+        $coursesections->close();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2013021801.01);
+    }
+
+    if ($oldversion < 2013021902.00) {
+        // ISO country change: Netherlands Antilles is split into BQ, CW & SX
+        // http://www.iso.org/iso/iso_3166-1_newsletter_vi-8_split_of_the_dutch_antilles_final-en.pdf
+        $sql = "UPDATE {user} SET country = '' WHERE country = ?";
+        $DB->execute($sql, array('AN'));
+
+        upgrade_main_savepoint(true, 2013021902.00);
+    }
+
     return true;
 }
index d5b84aa..3e54990 100644 (file)
@@ -1469,6 +1469,10 @@ function &get_mimetypes_array() {
         'fdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
         'flv'  => array ('type'=>'video/x-flv', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
         'f4v'  => array ('type'=>'video/mp4', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
+
+        'gallery'           => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
+        'galleryitem'       => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
+        'gallerycollection' => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
         'gif'  => array ('type'=>'image/gif', 'icon'=>'gif', 'groups'=>array('image', 'web_image'), 'string'=>'image'),
         'gtar' => array ('type'=>'application/x-gtar', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
         'tgz'  => array ('type'=>'application/g-zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive'),
@@ -1512,6 +1516,9 @@ function &get_mimetypes_array() {
         'mpe'  => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
         'mpg'  => array ('type'=>'video/mpeg', 'icon'=>'mpeg', 'groups'=>array('video','web_video'), 'string'=>'video'),
 
+        'nbk'       => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
+        'notebook'  => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
+
         'odt'  => array ('type'=>'application/vnd.oasis.opendocument.text', 'icon'=>'writer', 'groups'=>array('document')),
         'ott'  => array ('type'=>'application/vnd.oasis.opendocument.text-template', 'icon'=>'writer', 'groups'=>array('document')),
         'oth'  => array ('type'=>'application/vnd.oasis.opendocument.text-web', 'icon'=>'oth', 'groups'=>array('document')),
@@ -1591,6 +1598,8 @@ function &get_mimetypes_array() {
         'webm'  => array ('type'=>'video/webm', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
         'wmv'  => array ('type'=>'video/x-ms-wmv', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
         'asf'  => array ('type'=>'video/x-ms-asf', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
+
+        'xbk'  => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
         'xdp'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
         'xfd'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
         'xfdf' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
@@ -1605,6 +1614,7 @@ function &get_mimetypes_array() {
 
         'xml'  => array ('type'=>'application/xml', 'icon'=>'markup'),
         'xsl'  => array ('type'=>'text/xml', 'icon'=>'markup'),
+
         'zip'  => array ('type'=>'application/zip', 'icon'=>'archive', 'groups'=>array('archive'), 'string'=>'archive')
     );
     return $mimearray;
@@ -2935,14 +2945,22 @@ class curl {
     }
 
     /**
-     * Set curl options
+     * Set curl options.
      *
-     * @param array $options If array is null, this function will
-     * reset the options to default value.
+     * Do not use the curl constants to define the options, pass a string
+     * corresponding to that constant. Ie. to set CURLOPT_MAXREDIRS, pass
+     * array('CURLOPT_MAXREDIRS' => 10) or array('maxredirs' => 10) to this method.
+     *
+     * @param array $options If array is null, this function will reset the options to default value.
+     * @return void
+     * @throws coding_exception If an option uses constant value instead of option name.
      */
     public function setopt($options = array()) {
         if (is_array($options)) {
-            foreach($options as $name => $val){
+            foreach ($options as $name => $val){
+                if (!is_string($name)) {
+                    throw new coding_exception('Curl options should be defined using strings, not constant values.');
+                }
                 if (stripos($name, 'CURLOPT_') === false) {
                     $name = strtoupper('CURLOPT_'.$name);
                 }
@@ -3067,11 +3085,9 @@ class curl {
             var_dump($this->header);
         }
 
-        // set options
+        // Set options.
         foreach($this->options as $name => $val) {
-            if (is_string($name)) {
-                $name = constant(strtoupper($name));
-            }
+            $name = constant(strtoupper($name));
             curl_setopt($curl, $name, $val);
         }
         return $curl;
index f723baa..dfb70d8 100644 (file)
@@ -1935,8 +1935,8 @@ class file_storage {
         $referencehash = sha1($reference);
 
         $sql = "SELECT repositoryid, id FROM {files_reference}
-                 WHERE referencehash = ? and reference = ?";
-        $rs = $DB->get_recordset_sql($sql, array($referencehash, $reference));
+                 WHERE referencehash = ?";
+        $rs = $DB->get_recordset_sql($sql, array($referencehash));
 
         $now = time();
         foreach ($rs as $record) {
index b8760c0..a01de83 100644 (file)
@@ -90,6 +90,25 @@ class filter_manager {
         return self::$singletoninstance;
     }
 
+    /**
+     * Resets the caches, usually to be called between unit tests
+     */
+    public static function reset_caches() {
+        if (self::$singletoninstance) {
+            self::$singletoninstance->unload_all_filters();
+        }
+        self::$singletoninstance = null;
+    }
+
+    /**
+     * Unloads all filters and other cached information
+     */
+    protected function unload_all_filters() {
+        $this->textfilters = array();
+        $this->stringfilters = array();
+        $this->stringfilternames = array();
+    }
+
     /**
      * Load all the filters required by this context.
      *
@@ -286,6 +305,16 @@ class performance_measuring_filter_manager extends filter_manager {
     protected $textsfiltered = 0;
     protected $stringsfiltered = 0;
 
+    /**
+     * Unloads all filters and other cached information
+     */
+    protected function unload_all_filters() {
+        parent::unload_all_filters();
+        $this->filterscreated = 0;
+        $this->textsfiltered = 0;
+        $this->stringsfiltered = 0;
+    }
+
     /**
      * @param string $filtername
      * @param object $context
diff --git a/lib/google/Google_Client.php b/lib/google/Google_Client.php
new file mode 100644 (file)
index 0000000..e6026a1
--- /dev/null
@@ -0,0 +1,453 @@
+<?php
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Check for the required json and curl extensions, the Google APIs PHP Client
+// won't function without them.
+if (! function_exists('curl_init')) {
+  throw new Exception('Google PHP API Client requires the CURL PHP extension');
+}
+
+if (! function_exists('json_decode')) {
+  throw new Exception('Google PHP API Client requires the JSON PHP extension');
+}
+
+if (! function_exists('http_build_query')) {
+  throw new Exception('Google PHP API Client requires http_build_query()');
+}
+
+if (! ini_get('date.timezone') && function_exists('date_default_timezone_set')) {
+  date_default_timezone_set('UTC');
+}
+
+// hack around with the include paths a bit so the library 'just works'
+set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path());
+
+require_once "config.php";
+// If a local configuration file is found, merge it's values with the default configuration
+if (file_exists(dirname(__FILE__)  . '/local_config.php')) {
+  $defaultConfig = $apiConfig;
+  require_once (dirname(__FILE__)  . '/local_config.php');
+  $apiConfig = array_merge($defaultConfig, $apiConfig);
+}
+
+// Include the top level classes, they each include their own dependencies
+require_once 'service/Google_Model.php';
+require_once 'service/Google_Service.php';
+require_once 'service/Google_ServiceResource.php';
+require_once 'auth/Google_AssertionCredentials.php';
+require_once 'auth/Google_Signer.php';
+require_once 'auth/Google_P12Signer.php';
+require_once 'service/Google_BatchRequest.php';
+require_once 'external/URITemplateParser.php';
+require_once 'auth/Google_Auth.php';
+require_once 'cache/Google_Cache.php';
+require_once 'io/Google_IO.php';
+require_once('service/Google_MediaFileUpload.php');
+
+/**
+ * The Google API Client
+ * http://code.google.com/p/google-api-php-client/
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ * @author Chirag Shah <chirags@google.com>
+ */
+class Google_Client {
+  /**
+   * @static
+   * @var Google_Auth $auth
+   */
+  static $auth;
+
+  /**
+   * @static
+   * @var Google_IO $io
+   */
+  static $io;
+
+  /**
+   * @static
+   * @var Google_Cache $cache
+   */
+  static $cache;
+
+  /**
+   * @static
+   * @var boolean $useBatch
+   */
+  static $useBatch = false;
+
+  /** @var array $scopes */
+  protected $scopes = array();
+
+  /** @var bool $useObjects */
+  protected $useObjects = false;
+
+  // definitions of services that are discovered.
+  protected $services = array();
+
+  // Used to track authenticated state, can't discover services after doing authenticate()
+  private $authenticated = false;
+
+  public function __construct($config = array()) {
+    global $apiConfig;
+    $apiConfig = array_merge($apiConfig, $config);
+    self::$cache = new $apiConfig['cacheClass']();
+    self::$auth = new $apiConfig['authClass']();
+    self::$io = new $apiConfig['ioClass']();
+  }
+
+  /**
+   * Add a service
+   */
+  public function addService($service, $version = false) {
+    global $apiConfig;
+    if ($this->authenticated) {
+      throw new Google_Exception('Cant add services after having authenticated');
+    }
+    $this->services[$service] = array();
+    if (isset($apiConfig['services'][$service])) {
+      // Merge the service descriptor with the default values
+      $this->services[$service] = array_merge($this->services[$service], $apiConfig['services'][$service]);
+    }
+  }
+
+  public function authenticate($code = null) {
+    $service = $this->prepareService();
+    $this->authenticated = true;
+    return self::$auth->authenticate($service, $code);
+  }
+
+  /**
+   * @return array
+   * @visible For Testing
+   */
+  public function prepareService() {
+    $service = array();
+    $scopes = array();
+    if ($this->scopes) {
+      $scopes = $this->scopes;
+    } else {
+      foreach ($this->services as $key => $val) {
+        if (isset($val['scope'])) {
+          if (is_array($val['scope'])) {
+            $scopes = array_merge($val['scope'], $scopes);
+          } else {
+            $scopes[] = $val['scope'];
+          }
+        } else {
+          $scopes[] = 'https://www.googleapis.com/auth/' . $key;
+        }
+        unset($val['discoveryURI']);
+        unset($val['scope']);
+        $service = array_merge($service, $val);
+      }
+    }
+    $service['scope'] = implode(' ', $scopes);
+    return $service;
+  }
+
+  /**
+   * Set the OAuth 2.0 access token using the string that resulted from calling authenticate()
+   * or Google_Client#getAccessToken().
+   * @param string $accessToken JSON encoded string containing in the following format:
+   * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
+   *  "expires_in":3600, "id_token":"TOKEN", "created":1320790426}
+   */
+  public function setAccessToken($accessToken) {
+    if ($accessToken == null || 'null' == $accessToken) {
+      $accessToken = null;
+    }
+    self::$auth->setAccessToken($accessToken);
+  }
+
+  /**
+   * Set the type of Auth class the client should use.
+   * @param string $authClassName
+   */
+  public function setAuthClass($authClassName) {
+    self::$auth = new $authClassName();
+  }
+
+  /**
+   * Construct the OAuth 2.0 authorization request URI.
+   * @return string
+   */
+  public function createAuthUrl() {
+    $service = $this->prepareService();
+    return self::$auth->createAuthUrl($service['scope']);
+  }
+
+  /**
+   * Get the OAuth 2.0 access token.
+   * @return string $accessToken JSON encoded string in the following format:
+   * {"access_token":"TOKEN", "refresh_token":"TOKEN", "token_type":"Bearer",
+   *  "expires_in":3600,"id_token":"TOKEN", "created":1320790426}
+   */
+  public function getAccessToken() {
+    $token = self::$auth->getAccessToken();
+    return (null == $token || 'null' == $token) ? null : $token;
+  }
+
+  /**
+   * Returns if the access_token is expired.
+   * @return bool Returns True if the access_token is expired.
+   */
+  public function isAccessTokenExpired() {
+    return self::$auth->isAccessTokenExpired();
+  }
+
+  /**
+   * Set the developer key to use, these are obtained through the API Console.
+   * @see http://code.google.com/apis/console-help/#generatingdevkeys
+   * @param string $developerKey
+   */
+  public function setDeveloperKey($developerKey) {
+    self::$auth->setDeveloperKey($developerKey);
+  }
+
+  /**
+   * Set OAuth 2.0 "state" parameter to achieve per-request customization.
+   * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
+   * @param string $state
+   */
+  public function setState($state) {
+    self::$auth->setState($state);
+  }
+
+  /**
+   * @param string $accessType Possible values for access_type include:
+   *  {@code "offline"} to request offline access from the user. (This is the default value)
+   *  {@code "online"} to request online access from the user.
+   */
+  public function setAccessType($accessType) {
+    self::$auth->setAccessType($accessType);
+  }
+
+  /**
+   * @param string $approvalPrompt Possible values for approval_prompt include:
+   *  {@code "force"} to force the approval UI to appear. (This is the default value)
+   *  {@code "auto"} to request auto-approval when possible.
+   */
+  public function setApprovalPrompt($approvalPrompt) {
+    self::$auth->setApprovalPrompt($approvalPrompt);
+  }
+
+  /**
+   * Set the application name, this is included in the User-Agent HTTP header.
+   * @param string $applicationName
+   */
+  public function setApplicationName($applicationName) {
+    global $apiConfig;
+    $apiConfig['application_name'] = $applicationName;
+  }
+
+  /**
+   * Set the OAuth 2.0 Client ID.
+   * @param string $clientId
+   */
+  public function setClientId($clientId) {
+    global $apiConfig;
+    $apiConfig['oauth2_client_id'] = $clientId;
+    self::$auth->clientId = $clientId;
+  }
+
+  /**
+   * Get the OAuth 2.0 Client ID.
+   */
+  public function getClientId() {
+    return self::$auth->clientId;
+  }
+  
+  /**
+   * Set the OAuth 2.0 Client Secret.
+   * @param string $clientSecret
+   */
+  public function setClientSecret($clientSecret) {
+    global $apiConfig;
+    $apiConfig['oauth2_client_secret'] = $clientSecret;
+    self::$auth->clientSecret = $clientSecret;
+  }
+
+  /**
+   * Get the OAuth 2.0 Client Secret.
+   */
+  public function getClientSecret() {
+    return self::$auth->clientSecret;
+  }
+
+  /**
+   * Set the OAuth 2.0 Redirect URI.
+   * @param string $redirectUri
+   */
+  public function setRedirectUri($redirectUri) {
+    global $apiConfig;
+    $apiConfig['oauth2_redirect_uri'] = $redirectUri;
+    self::$auth->redirectUri = $redirectUri;
+  }
+
+  /**
+   * Get the OAuth 2.0 Redirect URI.
+   */
+  public function getRedirectUri() {
+    return self::$auth->redirectUri;
+  }
+
+  /**
+   * Fetches a fresh OAuth 2.0 access token with the given refresh token.
+   * @param string $refreshToken
+   * @return void
+   */
+  public function refreshToken($refreshToken) {
+    self::$auth->refreshToken($refreshToken);
+  }
+
+  /**
+   * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
+   * token, if a token isn't provided.
+   * @throws Google_AuthException
+   * @param string|null $token The token (access token or a refresh token) that should be revoked.
+   * @return boolean Returns True if the revocation was successful, otherwise False.
+   */
+  public function revokeToken($token = null) {
+    self::$auth->revokeToken($token);
+  }
+
+  /**
+   * Verify an id_token. This method will verify the current id_token, if one
+   * isn't provided.
+   * @throws Google_AuthException
+   * @param string|null $token The token (id_token) that should be verified.
+   * @return Google_LoginTicket Returns an apiLoginTicket if the verification was
+   * successful.
+   */
+  public function verifyIdToken($token = null) {
+    return self::$auth->verifyIdToken($token);
+  }
+
+  /**
+   * @param Google_AssertionCredentials $creds
+   * @return void
+   */
+  public function setAssertionCredentials(Google_AssertionCredentials $creds) {
+    self::$auth->setAssertionCredentials($creds);
+  }
+
+  /**
+   * This function allows you to overrule the automatically generated scopes,
+   * so that you can ask for more or less permission in the auth flow
+   * Set this before you call authenticate() though!
+   * @param array $scopes, ie: array('https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/moderator')
+   */
+  public function setScopes($scopes) {
+    $this->scopes = is_string($scopes) ? explode(" ", $scopes) : $scopes;
+  }
+
+  /**
+   * Declare if objects should be returned by the api service classes.
+   *
+   * @param boolean $useObjects True if objects should be returned by the service classes.
+   * False if associative arrays should be returned (default behavior).
+   * @experimental
+   */
+  public function setUseObjects($useObjects) {
+    global $apiConfig;
+    $apiConfig['use_objects'] = $useObjects;
+  }
+
+  /**
+   * Declare if objects should be returned by the api service classes.
+   *
+   * @param boolean $useBatch True if the experimental batch support should
+   * be enabled. Defaults to False.
+   * @experimental
+   */
+  public function setUseBatch($useBatch) {
+    self::$useBatch = $useBatch;
+  }
+
+  /**
+   * @static
+   * @return Google_Auth the implementation of apiAuth.
+   */
+  public static function getAuth() {
+    return Google_Client::$auth;
+  }
+
+  /**
+   * @static
+   * @return Google_IO the implementation of apiIo.
+   */
+  public static function getIo() {
+    return Google_Client::$io;
+  }
+
+  /**
+   * @return Google_Cache the implementation of apiCache.
+   */
+  public function getCache() {
+    return Google_Client::$cache;
+  }
+}
+
+// Exceptions that the Google PHP API Library can throw
+class Google_Exception extends Exception {}
+class Google_AuthException extends Google_Exception {}
+class Google_CacheException extends Google_Exception {}
+class Google_IOException extends Google_Exception {}
+class Google_ServiceException extends Google_Exception {
+  /**
+   * Optional list of errors returned in a JSON body of an HTTP error response.
+   */
+  protected $errors = array();
+
+  /**
+   * Override default constructor to add ability to set $errors.
+   *
+   * @param string $message
+   * @param int $code
+   * @param Exception|null $previous
+   * @param [{string, string}] errors List of errors returned in an HTTP
+   * response.  Defaults to [].
+   */
+  public function __construct($message, $code = 0, Exception $previous = null,
+                              $errors = array()) {
+    if(version_compare(PHP_VERSION, '5.3.0') >= 0) {
+      parent::__construct($message, $code, $previous);
+    } else {
+      parent::__construct($message, $code);
+    }
+    
+    $this->errors = $errors;
+  }
+
+  /**
+   * An example of the possible errors returned.
+   *
+   * {
+   *   "domain": "global",
+   *   "reason": "authError",
+   *   "message": "Invalid Credentials",
+   *   "locationType": "header",
+   *   "location": "Authorization",
+   * }
+   *
+   * @return [{string, string}] List of errors return in an HTTP response or [].
+   */
+  public function getErrors() {
+    return $this->errors;
+  }
+}
diff --git a/lib/google/LICENSE b/lib/google/LICENSE
new file mode 100644 (file)
index 0000000..a148ba5
--- /dev/null
@@ -0,0 +1,203 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all
+other entities that control, are controlled by, or are under common
+control with that entity. For the purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation
+source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but
+not limited to compiled object code, generated documentation,
+and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or
+Object form, made available under the License, as indicated by a
+copyright notice that is included in or attached to the work
+(an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object
+form, that is based on (or derived from) the Work and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship. For the purposes
+of this License, Derivative Works shall not include works that remain
+separable from, or merely link (or bind by name) to the interfaces of,
+the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including
+the original version of the Work and any modifications or additions
+to that Work or Derivative Works thereof, that is intentionally
+submitted to Licensor for inclusion in the Work by the copyright owner
+or by an individual or Legal Entity authorized to submit on behalf of
+the copyright owner. For the purposes of this definition, "submitted"
+means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems,
+and issue tracking systems that are managed by, or on behalf of, the
+Licensor for the purpose of discussing and improving the Work, but
+excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work,
+where such license applies only to those patent claims licensable
+by such Contributor that are necessarily infringed by their
+Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You
+institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work
+or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses
+granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof in any medium, with or without
+modifications, and in Source or Object form, provided that You
+meet the following conditions:
+
+(a) You must give any other recipients of the Work or
+Derivative Works a copy of this License; and
+
+(b) You must cause any modified files to carry prominent notices
+stating that You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works
+that You distribute, all copyright, patent, trademark, and
+attribution notices from the Source form of the Work,
+excluding those notices that do not pertain to any part of
+the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its
+distribution, then any Derivative Works that You distribute must
+include a readable copy of the attribution notices contained
+within such NOTICE file, excluding those notices that do not
+pertain to any part of the Derivative Works, in at least one
+of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or
+documentation, if provided along with the Derivative Works; or,
+within a display generated by the Derivative Works, if and
+wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and
+do not modify the License. You may add Your own attribution
+notices within Derivative Works that You distribute, alongside
+or as an addendum to the NOTICE text from the Work, provided
+that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and
+may provide additional or different license terms and conditions
+for use, reproduction, or distribution of Your modifications, or
+for any such Derivative Works as a whole, provided Your use,
+reproduction, and distribution of the Work otherwise complies with
+the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
diff --git a/lib/google/NOTICE b/lib/google/NOTICE
new file mode 100644 (file)
index 0000000..22d7cb5
--- /dev/null
@@ -0,0 +1,4 @@
+This product contains the following libraries:
+
+XRDS-Simple library from http://code.google.com/p/diso/
+Apache License 2.0
diff --git a/lib/google/README b/lib/google/README
new file mode 100644 (file)
index 0000000..42c42c0
--- /dev/null
@@ -0,0 +1,40 @@
+Google APIs Client Library for PHP
+=====================================
+
+== Description
+The Google API Client Library enables you to work with Google APIs such as Google+, Drive, Tasks, or Latitude on your server.
+
+Requirements:
+  PHP 5.2.x or higher [http://www.php.net/]
+  PHP Curl extension [http://www.php.net/manual/en/intro.curl.php]
+  PHP JSON extension [http://php.net/manual/en/book.json.php]
+
+Project page:
+  http://code.google.com/p/google-api-php-client
+
+OAuth 2 instructions:
+  http://code.google.com/p/google-api-php-client/wiki/OAuth2
+
+Report a defect or feature request here:
+  http://code.google.com/p/google-api-php-client/issues/entry
+
+Subscribe to project updates in your feed reader:
+  http://code.google.com/feeds/p/google-api-php-client/updates/basic
+
+Supported sample applications:
+  http://code.google.com/p/google-api-php-client/wiki/Samples
+
+== Basic Example
+  <?php
+  require_once 'path/to/src/Google_Client.php';
+  require_once 'path/to/src/contrib/apiBooksService.php';
+
+  $client = new Google_Client();
+  $service = new Google_BooksService($client);
+
+  $optParams = array('filter' => 'free-ebooks');
+  $results = $service->volumes->listVolumes('Henry David Thoreau', $optParams);
+
+  foreach ($results['items'] as $item) {
+    print($item['volumeInfo']['title'] . '<br>');
+  }
diff --git a/lib/google/auth/Google_AssertionCredentials.php b/lib/google/auth/Google_AssertionCredentials.php
new file mode 100644 (file)
index 0000000..0d7aeb3
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Credentials object used for OAuth 2.0 Signed JWT assertion grants.
+ *
+ * @author Chirag Shah <chirags@google.com>
+ */
+class Google_AssertionCredentials {
+  const MAX_TOKEN_LIFETIME_SECS = 3600;
+
+  public $serviceAccountName;
+  public $scopes;
+  public $privateKey;
+  public $privateKeyPassword;
+  public $assertionType;
+  public $prn;
+
+  /**
+   * @param $serviceAccountName
+   * @param $scopes array List of scopes
+   * @param $privateKey
+   * @param string $privateKeyPassword
+   * @param string $assertionType
+   * @param bool|string $prn The email address of the user for which the
+   *               application is requesting delegated access.
+   */
+  public function __construct(
+      $serviceAccountName,
+      $scopes,
+      $privateKey,
+      $privateKeyPassword = 'notasecret',
+      $assertionType = 'http://oauth.net/grant_type/jwt/1.0/bearer',
+      $prn = false) {
+    $this->serviceAccountName = $serviceAccountName;
+    $this->scopes = is_string($scopes) ? $scopes : implode(' ', $scopes);
+    $this->privateKey = $privateKey;
+    $this->privateKeyPassword = $privateKeyPassword;
+    $this->assertionType = $assertionType;
+    $this->prn = $prn;
+  }
+
+  public function generateAssertion() {
+    $now = time();
+
+    $jwtParams = array(
+          'aud' => Google_OAuth2::OAUTH2_TOKEN_URI,
+          'scope' => $this->scopes,
+          'iat' => $now,
+          'exp' => $now + self::MAX_TOKEN_LIFETIME_SECS,
+          'iss' => $this->serviceAccountName,
+    );
+
+    if ($this->prn !== false) {
+      $jwtParams['prn'] = $this->prn;
+    }
+
+    return $this->makeSignedJwt($jwtParams);
+  }
+
+  /**
+   * Creates a signed JWT.
+   * @param array $payload
+   * @return string The signed JWT.
+   */
+  private function makeSignedJwt($payload) {
+    $header = array('typ' => 'JWT', 'alg' => 'RS256');
+
+    $segments = array(
+      Google_Utils::urlSafeB64Encode(json_encode($header)),
+      Google_Utils::urlSafeB64Encode(json_encode($payload))
+    );
+
+    $signingInput = implode('.', $segments);
+    $signer = new Google_P12Signer($this->privateKey, $this->privateKeyPassword);
+    $signature = $signer->sign($signingInput);
+    $segments[] = Google_Utils::urlSafeB64Encode($signature);
+
+    return implode(".", $segments);
+  }
+}
diff --git a/lib/google/auth/Google_Auth.php b/lib/google/auth/Google_Auth.php
new file mode 100644 (file)
index 0000000..010782d
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once "Google_AuthNone.php";
+require_once "Google_OAuth2.php";
+
+/**
+ * Abstract class for the Authentication in the API client
+ * @author Chris Chabot <chabotc@google.com>
+ *
+ */
+abstract class Google_Auth {
+  abstract public function authenticate($service);
+  abstract public function sign(Google_HttpRequest $request);
+  abstract public function createAuthUrl($scope);
+
+  abstract public function getAccessToken();
+  abstract public function setAccessToken($accessToken);
+  abstract public function setDeveloperKey($developerKey);
+  abstract public function refreshToken($refreshToken);
+  abstract public function revokeToken();
+}
diff --git a/lib/google/auth/Google_AuthNone.php b/lib/google/auth/Google_AuthNone.php
new file mode 100644 (file)
index 0000000..6ca6bc2
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Do-nothing authentication implementation, use this if you want to make un-authenticated calls
+ * @author Chris Chabot <chabotc@google.com>
+ * @author Chirag Shah <chirags@google.com>
+ */
+class Google_AuthNone extends Google_Auth {
+  public $key = null;
+
+  public function __construct() {
+    global $apiConfig;
+    if (!empty($apiConfig['developer_key'])) {
+      $this->setDeveloperKey($apiConfig['developer_key']);
+    }
+  }
+
+  public function setDeveloperKey($key) {$this->key = $key;}
+  public function authenticate($service) {/*noop*/}
+  public function setAccessToken($accessToken) {/* noop*/}
+  public function getAccessToken() {return null;}
+  public function createAuthUrl($scope) {return null;}
+  public function refreshToken($refreshToken) {/* noop*/}
+  public function revokeToken() {/* noop*/}
+
+  public function sign(Google_HttpRequest $request) {
+    if ($this->key) {
+      $request->setUrl($request->getUrl() . ((strpos($request->getUrl(), '?') === false) ? '?' : '&')
+          . 'key='.urlencode($this->key));
+    }
+    return $request;
+  }
+}
diff --git a/lib/google/auth/Google_LoginTicket.php b/lib/google/auth/Google_LoginTicket.php
new file mode 100644 (file)
index 0000000..c0ce614
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Class to hold information about an authenticated login.
+ *
+ * @author Brian Eaton <beaton@google.com>
+ */
+class Google_LoginTicket {
+  const USER_ATTR = "id";
+
+  // Information from id token envelope.
+  private $envelope;
+
+  // Information from id token payload.
+  private $payload;
+
+  /**
+   * Creates a user based on the supplied token.
+   *
+   * @param string $envelope Header from a verified authentication token.
+   * @param string $payload Information from a verified authentication token.
+   */
+  public function __construct($envelope, $payload) {
+    $this->envelope = $envelope;
+    $this->payload = $payload;
+  }
+
+  /**
+   * Returns the numeric identifier for the user.
+   * @throws Google_AuthException
+   * @return
+   */
+  public function getUserId() {
+    if (array_key_exists(self::USER_ATTR, $this->payload)) {
+      return $this->payload[self::USER_ATTR];
+    }
+    throw new Google_AuthException("No user_id in token");
+  }
+
+  /**
+   * Returns attributes from the login ticket.  This can contain
+   * various information about the user session.
+   * @return array
+   */
+  public function getAttributes() {
+    return array("envelope" => $this->envelope, "payload" => $this->payload);
+  }
+}
diff --git a/lib/google/auth/Google_OAuth2.php b/lib/google/auth/Google_OAuth2.php
new file mode 100644 (file)
index 0000000..7394316
--- /dev/null
@@ -0,0 +1,444 @@
+<?php
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once "Google_Verifier.php";
+require_once "Google_LoginTicket.php";
+require_once "service/Google_Utils.php";
+
+/**
+ * Authentication class that deals with the OAuth 2 web-server authentication flow
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ * @author Chirag Shah <chirags@google.com>
+ *
+ */
+class Google_OAuth2 extends Google_Auth {
+  public $clientId;
+  public $clientSecret;
+  public $developerKey;
+  public $token;
+  public $redirectUri;
+  public $state;
+  public $accessType = 'offline';
+  public $approvalPrompt = 'force';
+
+  /** @var Google_AssertionCredentials $assertionCredentials */
+  public $assertionCredentials;
+
+  const OAUTH2_REVOKE_URI = 'https://accounts.google.com/o/oauth2/revoke';
+  const OAUTH2_TOKEN_URI = 'https://accounts.google.com/o/oauth2/token';
+  const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/auth';
+  const OAUTH2_FEDERATED_SIGNON_CERTS_URL = 'https://www.googleapis.com/oauth2/v1/certs';
+  const CLOCK_SKEW_SECS = 300; // five minutes in seconds
+  const AUTH_TOKEN_LIFETIME_SECS = 300; // five minutes in seconds
+  const MAX_TOKEN_LIFETIME_SECS = 86400; // one day in seconds
+
+  /**
+   * Instantiates the class, but does not initiate the login flow, leaving it
+   * to the discretion of the caller (which is done by calling authenticate()).
+   */
+  public function __construct() {
+    global $apiConfig;
+    
+    if (! empty($apiConfig['developer_key'])) {
+      $this->developerKey = $apiConfig['developer_key'];
+    }
+
+    if (! empty($apiConfig['oauth2_client_id'])) {
+      $this->clientId = $apiConfig['oauth2_client_id'];
+    }
+
+    if (! empty($apiConfig['oauth2_client_secret'])) {
+      $this->clientSecret = $apiConfig['oauth2_client_secret'];
+    }
+
+    if (! empty($apiConfig['oauth2_redirect_uri'])) {
+      $this->redirectUri = $apiConfig['oauth2_redirect_uri'];
+    }
+    
+    if (! empty($apiConfig['oauth2_access_type'])) {
+      $this->accessType = $apiConfig['oauth2_access_type'];
+    }
+
+    if (! empty($apiConfig['oauth2_approval_prompt'])) {
+      $this->approvalPrompt = $apiConfig['oauth2_approval_prompt'];
+    }
+  }
+
+  /**
+   * @param $service
+   * @param string|null $code
+   * @throws Google_AuthException
+   * @return string
+   */
+  public function authenticate($service, $code = null) {
+    if (!$code && isset($_GET['code'])) {
+      $code = $_GET['code'];
+    }
+
+    if ($code) {
+      // We got here from the redirect from a successful authorization grant, fetch the access token
+      $request = Google_Client::$io->makeRequest(new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), array(
+          'code' => $code,
+          'grant_type' => 'authorization_code',
+          'redirect_uri' => $this->redirectUri,
+          'client_id' => $this->clientId,
+          'client_secret' => $this->clientSecret
+      )));
+
+      if ($request->getResponseHttpCode() == 200) {
+        $this->setAccessToken($request->getResponseBody());
+        $this->token['created'] = time();
+        return $this->getAccessToken();
+      } else {
+        $response = $request->getResponseBody();
+        $decodedResponse = json_decode($response, true);
+        if ($decodedResponse != null && $decodedResponse['error']) {
+          $response = $decodedResponse['error'];
+        }
+        throw new Google_AuthException("Error fetching OAuth2 access token, message: '$response'", $request->getResponseHttpCode());
+      }
+    }
+
+    $authUrl = $this->createAuthUrl($service['scope']);
+    header('Location: ' . $authUrl);
+    return true;
+  } 
+
+  /**
+   * Create a URL to obtain user authorization.
+   * The authorization endpoint allows the user to first
+   * authenticate, and then grant/deny the access request.
+   * @param string $scope The scope is expressed as a list of space-delimited strings.
+   * @return string
+   */
+  public function createAuthUrl($scope) {
+    $params = array(
+        'response_type=code',
+        'redirect_uri=' . urlencode($this->redirectUri),
+        'client_id=' . urlencode($this->clientId),
+        'scope=' . urlencode($scope),
+        'access_type=' . urlencode($this->accessType),
+        'approval_prompt=' . urlencode($this->approvalPrompt)
+    );
+
+    if (isset($this->state)) {
+      $params[] = 'state=' . urlencode($this->state);
+    }
+    $params = implode('&', $params);
+    return self::OAUTH2_AUTH_URL . "?$params";
+  }
+
+  /**
+   * @param string $token
+   * @throws Google_AuthException
+   */
+  public function setAccessToken($token) {
+    $token = json_decode($token, true);
+    if ($token == null) {
+      throw new Google_AuthException('Could not json decode the token');
+    }
+    if (! isset($token['access_token'])) {
+      throw new Google_AuthException("Invalid token format");
+    }
+    $this->token = $token;
+  }
+
+  public function getAccessToken() {
+    return json_encode($this->token);
+  }
+
+  public function setDeveloperKey($developerKey) {
+    $this->developerKey = $developerKey;
+  }
+
+  public function setState($state) {
+    $this->state = $state;
+  }
+
+  public function setAccessType($accessType) {
+    $this->accessType = $accessType;
+  }
+
+  public function setApprovalPrompt($approvalPrompt) {
+    $this->approvalPrompt = $approvalPrompt;
+  }
+
+  public function setAssertionCredentials(Google_AssertionCredentials $creds) {
+    $this->assertionCredentials = $creds;
+  }
+
+  /**
+   * Include an accessToken in a given apiHttpRequest.
+   * @param Google_HttpRequest $request
+   * @return Google_HttpRequest
+   * @throws Google_AuthException
+   */
+  public function sign(Google_HttpRequest $request) {
+    // add the developer key to the request before signing it
+    if ($this->developerKey) {
+      $requestUrl = $request->getUrl();
+      $requestUrl .= (strpos($request->getUrl(), '?') === false) ? '?' : '&';
+      $requestUrl .=  'key=' . urlencode($this->developerKey);
+      $request->setUrl($requestUrl);
+    }
+
+    // Cannot sign the request without an OAuth access token.
+    if (null == $this->token && null == $this->assertionCredentials) {
+      return $request;
+    }
+
+    // Check if the token is set to expire in the next 30 seconds
+    // (or has already expired).
+    if ($this->isAccessTokenExpired()) {
+      if ($this->assertionCredentials) {
+        $this->refreshTokenWithAssertion();
+      } else {
+        if (! array_key_exists('refresh_token', $this->token)) {
+            throw new Google_AuthException("The OAuth 2.0 access token has expired, "
+                . "and a refresh token is not available. Refresh tokens are not "
+                . "returned for responses that were auto-approved.");
+        }
+        $this->refreshToken($this->token['refresh_token']);
+      }
+    }
+
+    // Add the OAuth2 header to the request
+    $request->setRequestHeaders(
+        array('Authorization' => 'Bearer ' . $this->token['access_token'])
+    );
+
+    return $request;
+  }
+
+  /**
+   * Fetches a fresh access token with the given refresh token.
+   * @param string $refreshToken
+   * @return void
+   */
+  public function refreshToken($refreshToken) {
+    $this->refreshTokenRequest(array(
+        'client_id' => $this->clientId,
+        'client_secret' => $this->clientSecret,
+        'refresh_token' => $refreshToken,
+        'grant_type' => 'refresh_token'
+    ));
+  }
+
+  /**
+   * Fetches a fresh access token with a given assertion token.
+   * @param Google_AssertionCredentials $assertionCredentials optional.
+   * @return void
+   */
+  public function refreshTokenWithAssertion($assertionCredentials = null) {
+    if (!$assertionCredentials) {
+      $assertionCredentials = $this->assertionCredentials;
+    }
+
+    $this->refreshTokenRequest(array(
+        'grant_type' => 'assertion',
+        'assertion_type' => $assertionCredentials->assertionType,
+        'assertion' => $assertionCredentials->generateAssertion(),
+    ));
+  }
+
+  private function refreshTokenRequest($params) {
+    $http = new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), $params);
+    $request = Google_Client::$io->makeRequest($http);
+
+    $code = $request->getResponseHttpCode();
+    $body = $request->getResponseBody();
+    if (200 == $code) {
+      $token = json_decode($body, true);
+      if ($token == null) {
+        throw new Google_AuthException("Could not json decode the access token");
+      }
+
+      if (! isset($token['access_token']) || ! isset($token['expires_in'])) {
+        throw new Google_AuthException("Invalid token format");
+      }
+
+      $this->token['access_token'] = $token['access_token'];
+      $this->token['expires_in'] = $token['expires_in'];
+      $this->token['created'] = time();
+    } else {
+      throw new Google_AuthException("Error refreshing the OAuth2 token, message: '$body'", $code);
+    }
+  }
+
+    /**
+     * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
+     * token, if a token isn't provided.
+     * @throws Google_AuthException
+     * @param string|null $token The token (access token or a refresh token) that should be revoked.
+     * @return boolean Returns True if the revocation was successful, otherwise False.
+     */
+  public function revokeToken($token = null) {
+    if (!$token) {
+      $token = $this->token['access_token'];
+    }
+    $request = new Google_HttpRequest(self::OAUTH2_REVOKE_URI, 'POST', array(), "token=$token");
+    $response = Google_Client::$io->makeRequest($request);
+    $code = $response->getResponseHttpCode();
+    if ($code == 200) {
+      $this->token = null;
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns if the access_token is expired.
+   * @return bool Returns True if the access_token is expired.
+   */
+  public function isAccessTokenExpired() {
+    if (null == $this->token) {
+      return true;
+    }
+
+    // If the token is set to expire in the next 30 seconds.
+    $expired = ($this->token['created']
+        + ($this->token['expires_in'] - 30)) < time();
+
+    return $expired;
+  }
+
+  // Gets federated sign-on certificates to use for verifying identity tokens.
+  // Returns certs as array structure, where keys are key ids, and values
+  // are PEM encoded certificates.
+  private function getFederatedSignOnCerts() {
+    // This relies on makeRequest caching certificate responses.
+    $request = Google_Client::$io->makeRequest(new Google_HttpRequest(
+        self::OAUTH2_FEDERATED_SIGNON_CERTS_URL));
+    if ($request->getResponseHttpCode() == 200) {
+      $certs = json_decode($request->getResponseBody(), true);
+      if ($certs) {
+        return $certs;
+      }
+    }
+    throw new Google_AuthException(
+        "Failed to retrieve verification certificates: '" .
+            $request->getResponseBody() . "'.",
+        $request->getResponseHttpCode());
+  }
+
+  /**
+   * Verifies an id token and returns the authenticated apiLoginTicket.
+   * Throws an exception if the id token is not valid.
+   * The audience parameter can be used to control which id tokens are
+   * accepted.  By default, the id token must have been issued to this OAuth2 client.
+   *
+   * @param $id_token
+   * @param $audience
+   * @return Google_LoginTicket
+   */
+  public function verifyIdToken($id_token = null, $audience = null) {
+    if (!$id_token) {
+      $id_token = $this->token['id_token'];
+    }
+
+    $certs = $this->getFederatedSignonCerts();
+    if (!$audience) {
+      $audience = $this->clientId;
+    }
+    return $this->verifySignedJwtWithCerts($id_token, $certs, $audience);
+  }
+
+  // Verifies the id token, returns the verified token contents.
+  // Visible for testing.
+  function verifySignedJwtWithCerts($jwt, $certs, $required_audience) {
+    $segments = explode(".", $jwt);
+    if (count($segments) != 3) {
+      throw new Google_AuthException("Wrong number of segments in token: $jwt");
+    }
+    $signed = $segments[0] . "." . $segments[1];
+    $signature = Google_Utils::urlSafeB64Decode($segments[2]);
+
+    // Parse envelope.
+    $envelope = json_decode(Google_Utils::urlSafeB64Decode($segments[0]), true);
+    if (!$envelope) {
+      throw new Google_AuthException("Can't parse token envelope: " . $segments[0]);
+    }
+
+    // Parse token
+    $json_body = Google_Utils::urlSafeB64Decode($segments[1]);
+    $payload = json_decode($json_body, true);
+    if (!$payload) {
+      throw new Google_AuthException("Can't parse token payload: " . $segments[1]);
+    }
+
+    // Check signature
+    $verified = false;
+    foreach ($certs as $keyName => $pem) {
+      $public_key = new Google_PemVerifier($pem);
+      if ($public_key->verify($signed, $signature)) {
+        $verified = true;
+        break;
+      }
+    }
+
+    if (!$verified) {
+      throw new Google_AuthException("Invalid token signature: $jwt");
+    }
+
+    // Check issued-at timestamp
+    $iat = 0;
+    if (array_key_exists("iat", $payload)) {
+      $iat = $payload["iat"];
+    }
+    if (!$iat) {
+      throw new Google_AuthException("No issue time in token: $json_body");
+    }
+    $earliest = $iat - self::CLOCK_SKEW_SECS;
+
+    // Check expiration timestamp
+    $now = time();
+    $exp = 0;
+    if (array_key_exists("exp", $payload)) {
+      $exp = $payload["exp"];
+    }
+    if (!$exp) {
+      throw new Google_AuthException("No expiration time in token: $json_body");
+    }
+    if ($exp >= $now + self::MAX_TOKEN_LIFETIME_SECS) {
+      throw new Google_AuthException(
+          "Expiration time too far in future: $json_body");
+    }
+
+    $latest = $exp + self::CLOCK_SKEW_SECS;
+    if ($now < $earliest) {
+      throw new Google_AuthException(
+          "Token used too early, $now < $earliest: $json_body");
+    }
+    if ($now > $latest) {
+      throw new Google_AuthException(
+          "Token used too late, $now > $latest: $json_body");
+    }
+
+    // TODO(beaton): check issuer field?
+
+    // Check audience
+    $aud = $payload["aud"];
+    if ($aud != $required_audience) {
+      throw new Google_AuthException("Wrong recipient, $aud != $required_audience: $json_body");
+    }
+
+    // All good.
+    return new Google_LoginTicket($envelope, $payload);
+  }
+}
diff --git a/lib/google/auth/Google_P12Signer.php b/lib/google/auth/Google_P12Signer.php
new file mode 100644 (file)
index 0000000..1bed590
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Signs data.
+ *
+ * Only used for testing.
+ *
+ * @author Brian Eaton <beaton@google.com>
+ */
+class Google_P12Signer extends Google_Signer {
+  // OpenSSL private key resource
+  private $privateKey;
+
+  // Creates a new signer from a .p12 file.
+  function __construct($p12, $password) {
+    if (!function_exists('openssl_x509_read')) {
+      throw new Exception(
+          'The Google PHP API library needs the openssl PHP extension');
+    }
+
+    // This throws on error
+    $certs = array();
+    if (!openssl_pkcs12_read($p12, $certs, $password)) {
+      throw new Google_AuthException("Unable to parse the p12 file.  " .
+          "Is this a .p12 file?  Is the password correct?  OpenSSL error: " .
+          openssl_error_string());
+    }
+    // TODO(beaton): is this part of the contract for the openssl_pkcs12_read
+    // method?  What happens if there are multiple private keys?  Do we care?
+    if (!array_key_exists("pkey", $certs) || !$certs["pkey"]) {
+      throw new Google_AuthException("No private key found in p12 file.");
+    }
+    $this->privateKey = openssl_pkey_get_private($certs["pkey"]);
+    if (!$this->privateKey) {
+      throw new Google_AuthException("Unable to load private key in ");
+    }
+  }
+
+  function __destruct() {
+    if ($this->privateKey) {
+      openssl_pkey_free($this->privateKey);
+    }
+  }
+
+  function sign($data) {
+    if(version_compare(PHP_VERSION, '5.3.0') < 0) {
+      throw new Google_AuthException(
+        "PHP 5.3.0 or higher is required to use service accounts.");
+    }
+    if (!openssl_sign($data, $signature, $this->privateKey, "sha256")) {
+      throw new Google_AuthException("Unable to sign data");
+    }
+    return $signature;
+  }
+}
diff --git a/lib/google/auth/Google_PemVerifier.php b/lib/google/auth/Google_PemVerifier.php
new file mode 100644 (file)
index 0000000..6c1c85f
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Verifies signatures using PEM encoded certificates.
+ *
+ * @author Brian Eaton <beaton@google.com>
+ */
+class Google_PemVerifier extends Google_Verifier {
+  private $publicKey;
+
+  /**
+   * Constructs a verifier from the supplied PEM-encoded certificate.
+   *
+   * $pem: a PEM encoded certificate (not a file).
+   * @param $pem
+   * @throws Google_AuthException
+   * @throws Google_Exception
+   */
+  function __construct($pem) {
+    if (!function_exists('openssl_x509_read')) {
+      throw new Google_Exception('Google API PHP client needs the openssl PHP extension');
+    }
+    $this->publicKey = openssl_x509_read($pem);
+    if (!$this->publicKey) {
+      throw new Google_AuthException("Unable to parse PEM: $pem");
+    }
+  }
+
+  function __destruct() {
+    if ($this->publicKey) {
+      openssl_x509_free($this->publicKey);
+    }
+  }
+
+  /**
+   * Verifies the signature on data.
+   *
+   * Returns true if the signature is valid, false otherwise.
+   * @param $data
+   * @param $signature
+   * @throws Google_AuthException
+   * @return bool
+   */
+  function verify($data, $signature) {
+    $status = openssl_verify($data, $signature, $this->publicKey, "sha256");
+    if ($status === -1) {
+      throw new Google_AuthException('Signature verification error: ' . openssl_error_string());
+    }
+    return $status === 1;
+  }
+}
diff --git a/lib/google/auth/Google_Signer.php b/lib/google/auth/Google_Signer.php
new file mode 100644 (file)
index 0000000..7892baa
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once "Google_P12Signer.php";
+
+/**
+ * Signs data.
+ *
+ * @author Brian Eaton <beaton@google.com>
+ */
+abstract class Google_Signer {
+  /**
+   * Signs data, returns the signature as binary data.
+   */
+  abstract public function sign($data);
+}
diff --git a/lib/google/auth/Google_Verifier.php b/lib/google/auth/Google_Verifier.php
new file mode 100644 (file)
index 0000000..2839a37
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once "Google_PemVerifier.php";
+
+/**
+ * Verifies signatures.
+ *
+ * @author Brian Eaton <beaton@google.com>
+ */
+abstract class Google_Verifier {
+  /**
+   * Checks a signature, returns true if the signature is correct,
+   * false otherwise.
+   */
+  abstract public function verify($data, $signature);
+}
diff --git a/lib/google/cache/Google_ApcCache.php b/lib/google/cache/Google_ApcCache.php
new file mode 100644 (file)
index 0000000..3523c98
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A persistent storage class based on the APC cache, which is not
+ * really very persistent, as soon as you restart your web server
+ * the storage will be wiped, however for debugging and/or speed
+ * it can be useful, kinda, and cache is a lot cheaper then storage.
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ */
+class googleApcCache extends Google_Cache {
+
+  public function __construct() {
+    if (! function_exists('apc_add')) {
+      throw new Google_CacheException("Apc functions not available");
+    }
+  }
+
+  private function isLocked($key) {
+    if ((@apc_fetch($key . '.lock')) === false) {
+      return false;
+    }
+    return true;
+  }
+
+  private function createLock($key) {
+    // the interesting thing is that this could fail if the lock was created in the meantime..
+    // but we'll ignore that out of convenience
+    @apc_add($key . '.lock', '', 5);
+  }
+
+  private function removeLock($key) {
+    // suppress all warnings, if some other process removed it that's ok too
+    @apc_delete($key . '.lock');
+  }
+
+  private function waitForLock($key) {
+    // 20 x 250 = 5 seconds
+    $tries = 20;
+    $cnt = 0;
+    do {
+      // 250 ms is a long time to sleep, but it does stop the server from burning all resources on polling locks..
+      usleep(250);
+      $cnt ++;
+    } while ($cnt <= $tries && $this->isLocked($key));
+    if ($this->isLocked($key)) {
+      // 5 seconds passed, assume the owning process died off and remove it
+      $this->removeLock($key);
+    }
+  }
+
+   /**
+   * @inheritDoc
+   */
+  public function get($key, $expiration = false) {
+
+    if (($ret = @apc_fetch($key)) === false) {
+      return false;
+    }
+    if (!$expiration || (time() - $ret['time'] > $expiration)) {
+      $this->delete($key);
+      return false;
+    }
+    return unserialize($ret['data']);
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function set($key, $value) {
+    if (@apc_store($key, array('time' => time(), 'data' => serialize($value))) == false) {
+      throw new Google_CacheException("Couldn't store data");
+    }
+  }
+
+  /**
+   * @inheritDoc
+   * @param String $key
+   */
+  public function delete($key) {
+    @apc_delete($key);
+  }
+}
diff --git a/lib/google/cache/Google_Cache.php b/lib/google/cache/Google_Cache.php
new file mode 100644 (file)
index 0000000..809c55e
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+require_once "Google_FileCache.php";
+require_once "Google_MemcacheCache.php";
+
+/**
+ * Abstract storage class
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ */
+abstract class Google_Cache {
+
+  /**
+   * Retrieves the data for the given key, or false if they
+   * key is unknown or expired
+   *
+   * @param String $key The key who's data to retrieve
+   * @param boolean|int $expiration Expiration time in seconds
+   *
+   */
+  abstract function get($key, $expiration = false);
+
+  /**
+   * Store the key => $value set. The $value is serialized
+   * by this function so can be of any type
+   *
+   * @param string $key Key of the data
+   * @param string $value data
+   */
+  abstract function set($key, $value);
+
+  /**
+   * Removes the key/data pair for the given $key
+   *
+   * @param String $key
+   */
+  abstract function delete($key);
+}
+
+
diff --git a/lib/google/cache/Google_FileCache.php b/lib/google/cache/Google_FileCache.php
new file mode 100644 (file)
index 0000000..fbb1337
--- /dev/null
@@ -0,0 +1,137 @@
+<?php
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This class implements a basic on disk storage. While that does
+ * work quite well it's not the most elegant and scalable solution.
+ * It will also get you into a heap of trouble when you try to run
+ * this in a clustered environment. In those cases please use the
+ * MySql back-end
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ */
+class Google_FileCache extends Google_Cache {
+  private $path;
+
+  public function __construct() {
+    global $apiConfig;
+    $this->path = $apiConfig['ioFileCache_directory'];
+  }
+
+  private function isLocked($storageFile) {
+    // our lock file convention is simple: /the/file/path.lock
+    return file_exists($storageFile . '.lock');
+  }
+
+  private function createLock($storageFile) {
+    $storageDir = dirname($storageFile);
+    if (! is_dir($storageDir)) {
+      // @codeCoverageIgnoreStart
+      if (! @mkdir($storageDir, 0755, true)) {
+        // make sure the failure isn't because of a concurrency issue
+        if (! is_dir($storageDir)) {
+          throw new Google_CacheException("Could not create storage directory: $storageDir");
+        }
+      }
+      // @codeCoverageIgnoreEnd
+    }
+    @touch($storageFile . '.lock');
+  }
+
+  private function removeLock($storageFile) {
+    // suppress all warnings, if some other process removed it that's ok too
+    @unlink($storageFile . '.lock');
+  }
+
+  private function waitForLock($storageFile) {
+    // 20 x 250 = 5 seconds
+    $tries = 20;
+    $cnt = 0;
+    do {
+      // make sure PHP picks up on file changes. This is an expensive action but really can't be avoided
+      clearstatcache();
+      // 250 ms is a long time to sleep, but it does stop the server from burning all resources on polling locks..
+      usleep(250);
+      $cnt ++;
+    } while ($cnt <= $tries && $this->isLocked($storageFile));
+    if ($this->isLocked($storageFile)) {
+      // 5 seconds passed, assume the owning process died off and remove it
+      $this->removeLock($storageFile);
+    }
+  }
+
+  private function getCacheDir($hash) {
+    // use the first 2 characters of the hash as a directory prefix
+    // this should prevent slowdowns due to huge directory listings
+    // and thus give some basic amount of scalability
+    return $this->path . '/' . substr($hash, 0, 2);
+  }
+
+  private function getCacheFile($hash) {
+    return $this->getCacheDir($hash) . '/' . $hash;
+  }
+
+  public function get($key, $expiration = false) {
+    $storageFile = $this->getCacheFile(md5($key));
+    // See if this storage file is locked, if so we wait upto 5 seconds for the lock owning process to
+    // complete it's work. If the lock is not released within that time frame, it's cleaned up.
+    // This should give us a fair amount of 'Cache Stampeding' protection
+    if ($this->isLocked($storageFile)) {
+      $this->waitForLock($storageFile);
+    }
+    if (file_exists($storageFile) && is_readable($storageFile)) {
+      $now = time();
+      if (! $expiration || (($mtime = @filemtime($storageFile)) !== false && ($now - $mtime) < $expiration)) {
+        if (($data = @file_get_contents($storageFile)) !== false) {
+          $data = unserialize($data);
+          return $data;
+        }
+      }
+    }
+    return false;
+  }
+
+  public function set($key, $value) {
+    $storageDir = $this->getCacheDir(md5($key));
+    $storageFile = $this->getCacheFile(md5($key));
+    if ($this->isLocked($storageFile)) {
+      // some other process is writing to this file too, wait until it's done to prevent hickups
+      $this->waitForLock($storageFile);
+    }
+    if (! is_dir($storageDir)) {
+      if (! @mkdir($storageDir, 0755, true)) {
+        throw new Google_CacheException("Could not create storage directory: $storageDir");
+      }
+    }
+    // we serialize the whole request object, since we don't only want the
+    // responseContent but also the postBody used, headers, size, etc
+    $data = serialize($value);
+    $this->createLock($storageFile);
+    if (! @file_put_contents($storageFile, $data)) {
+      $this->removeLock($storageFile);
+      throw new Google_CacheException("Could not store data in the file");
+    }
+    $this->removeLock($storageFile);
+  }
+
+  public function delete($key) {
+    $file = $this->getCacheFile(md5($key));
+    if (! @unlink($file)) {
+      throw new Google_CacheException("Cache file could not be deleted");
+    }
+  }
+}
diff --git a/lib/google/cache/Google_MemcacheCache.php b/lib/google/cache/Google_MemcacheCache.php
new file mode 100644 (file)
index 0000000..22493f8
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A persistent storage class based on the memcache, which is not
+ * really very persistent, as soon as you restart your memcache daemon
+ * the storage will be wiped, however for debugging and/or speed
+ * it can be useful, kinda, and cache is a lot cheaper then storage.
+ *
+ * @author Chris Chabot <chabotc@google.com>
+ */
+class Google_MemcacheCache extends Google_Cache {
+  private $connection = false;
+
+  public function __construct() {
+    global $apiConfig;
+    if (! function_exists('memcache_connect')) {
+      throw new Google_CacheException("Memcache functions not available");
+    }
+    $this->host = $apiConfig['ioMemCacheCache_host'];
+    $this->port = $apiConfig['ioMemCacheCache_port'];
+    if (empty($this->host) || empty($this->port)) {
+      throw new Google_CacheException("You need to supply a valid memcache host and port");
+    }
+  }
+
+  private function isLocked($key) {
+    $this->check();
+    if ((@memcache_get($this->connection, $key . '.lock')) === false) {
+      return false;
+    }
+    return true;
+  }
+
+  private function createLock($key) {
+    $this->check();
+    // the interesting thing is that this could fail if the lock was created in the meantime..
+    // but we'll ignore that out of convenience
+    @memcache_add($this->connection, $key . '.lock', '', 0, 5);
+  }
+
+  private function removeLock($key) {
+    $this->check();
+    // suppress all warnings, if some other process removed it that's ok too
+    @memcache_delete($this->connection, $key . '.lock');
+  }
+
+  private function waitForLock($key) {
+    $this->check();
+    // 20 x 250 = 5 seconds
+    $tries = 20;
+    $cnt = 0;
+    do {
+      // 250 ms is a long time to sleep, but it does stop the server from burning all resources on polling locks..
+      usleep(250);
+      $cnt ++;
+    } while ($cnt <= $tries && $this->isLocked($key));
+    if ($this->isLocked($key)) {
+      // 5 seconds passed, assume the owning process died off and remove it
+      $this->removeLock($key);
+    }
+  }
+
+  // I prefer lazy initialization since the cache isn't used every request
+  // so this potentially saves a lot of overhead
+  private function connect() {
+    if (! $this->connection = @memcache_pconnect($this->host, $this->port)) {
+      throw new Google_CacheException("Couldn't connect to memcache server");
+    }
+  }
+
+  private function check() {
+    if (! $this->connection) {
+      $this->connect();
+    }
+  }
+
+  /**
+   * @inheritDoc
+   */
+  public function get($key, $expiration = false) {
+    $this->check();
+    if (($ret = @memcache_get($this->connection, $key)) === false) {
+      return false;
+    }
+    if (! $expiration || (time() - $ret['time'] > $expiration)) {
+      $this->delete($key);
+      return false;
+    }
+    return $ret['data'];
+  }
+
+  /**
+   * @inheritDoc
+   * @param string $key
+   * @param string $value
+   * @throws Google_CacheException
+   */
+  public function set($key, $value) {
+    $this->check();
+    // we store it with the cache_time default expiration so objects will at least get cleaned eventually.
+    if (@memcache_set($this->connection, $key, array('time' => time(),
+        'data' => $value), false) == false) {
+      throw new Google_CacheException("Couldn't store data in cache");
+    }
+  }
+
+  /**
+   * @inheritDoc
+   * @param String $key
+   */
+  public function delete($key) {
+    $this->check();
+    @memcache_delete($this->connection, $key);
+  }
+}
diff --git a/lib/google/config.php b/lib/google/config.php
new file mode 100644 (file)
index 0000000..00917f4
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+global $apiConfig;
+$apiConfig = array(
+    // True if objects should be returned by the service classes.
+    // False if associative arrays should be returned (default behavior).
+    'use_objects' => false,
+  
+    // The application_name is included in the User-Agent HTTP header.
+    'application_name' => '',
+
+    // OAuth2 Settings, you can get these keys at https://code.google.com/apis/console
+    'oauth2_client_id' => '',
+    'oauth2_client_secret' => '',
+    'oauth2_redirect_uri' => '',
+
+    // The developer key, you get this at https://code.google.com/apis/console
+    'developer_key' => '',
+  
+    // Site name to show in the Google's OAuth 1 authentication screen.
+    'site_name' => 'www.example.org',
+
+    // Which Authentication, Storage and HTTP IO classes to use.
+    'authClass'    => 'Google_OAuth2',
+    'ioClass'      => 'Google_CurlIO',
+    'cacheClass'   => 'Google_FileCache',
+
+    // Don't change these unless you're working against a special development or testing environment.
+    'basePath' => 'https://www.googleapis.com',
+
+    // IO Class dependent configuration, you only have to configure the values
+    // for the class that was configured as the ioClass above
+    'ioFileCache_directory'  =>
+        (function_exists('sys_get_temp_dir') ?
+            sys_get_temp_dir() . '/Google_Client' :
+        '/tmp/Google_Client'),
+
+    // Definition of service specific values like scopes, oauth token URLs, etc
+    'services' => array(
+      'analytics' => array('scope' => 'https://www.googleapis.com/auth/analytics.readonly'),
+      'calendar' => array(
+          'scope' => array(
+              "https://www.googleapis.com/auth/calendar",
+              "https://www.googleapis.com/auth/calendar.readonly",
+          )
+      ),
+      'books' => array('scope' => 'https://www.googleapis.com/auth/books'),
+      'latitude' => array(
+          'scope' => array(
+              'https://www.googleapis.com/auth/latitude.all.best',
+              'https://www.googleapis.com/auth/latitude.all.city',
+          )
+      ),
+      'moderator' => array('scope' => 'https://www.googleapis.com/auth/moderator'),
+      'oauth2' => array(
+          'scope' => array(
+              'https://www.googleapis.com/auth/userinfo.profile',
+              'https://www.googleapis.com/auth/userinfo.email',
+          )
+      ),
+      'plus' => array('scope' => 'https://www.googleapis.com/auth/plus.me'),
+      'siteVerification' => array('scope' => 'https://www.googleapis.com/auth/siteverification'),
+      'tasks' => array('scope' => 'https://www.googleapis.com/auth/tasks'),
+      'urlshortener' => array('scope' => 'https://www.googleapis.com/auth/urlshortener')
+    )
+);
\ No newline at end of file
diff --git a/lib/google/contrib/Google_AdexchangebuyerService.php b/lib/google/contrib/Google_AdexchangebuyerService.php
new file mode 100644 (file)
index 0000000..0fae208
--- /dev/null
@@ -0,0 +1,567 @@
+<?php
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+  /**
+   * The "directDeals" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adexchangebuyerService = new Google_AdexchangebuyerService(...);
+   *   $directDeals = $adexchangebuyerService->directDeals;
+   *  </code>
+   */
+  class Google_DirectDealsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Retrieves the authenticated user's list of direct deals. (directDeals.list)
+     *
+     * @param array $optParams Optional parameters.
+     * @return Google_DirectDealsList
+     */
+    public function listDirectDeals($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_DirectDealsList($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Gets one direct deal by ID. (directDeals.get)
+     *
+     * @param string $id The direct deal id
+     * @param array $optParams Optional parameters.
+     * @return Google_DirectDeal
+     */
+    public function get($id, $optParams = array()) {
+      $params = array('id' => $id);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_DirectDeal($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "accounts" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adexchangebuyerService = new Google_AdexchangebuyerService(...);
+   *   $accounts = $adexchangebuyerService->accounts;
+   *  </code>
+   */
+  class Google_AccountsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Updates an existing account. This method supports patch semantics. (accounts.patch)
+     *
+     * @param int $id The account id
+     * @param Google_Account $postBody
+     * @param array $optParams Optional parameters.
+     * @return Google_Account
+     */
+    public function patch($id, Google_Account $postBody, $optParams = array()) {
+      $params = array('id' => $id, 'postBody' => $postBody);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('patch', array($params));
+      if ($this->useObjects()) {
+        return new Google_Account($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Retrieves the authenticated user's list of accounts. (accounts.list)
+     *
+     * @param array $optParams Optional parameters.
+     * @return Google_AccountsList
+     */
+    public function listAccounts($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AccountsList($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Updates an existing account. (accounts.update)
+     *
+     * @param int $id The account id
+     * @param Google_Account $postBody
+     * @param array $optParams Optional parameters.
+     * @return Google_Account
+     */
+    public function update($id, Google_Account $postBody, $optParams = array()) {
+      $params = array('id' => $id, 'postBody' => $postBody);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('update', array($params));
+      if ($this->useObjects()) {
+        return new Google_Account($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Gets one account by ID. (accounts.get)
+     *
+     * @param int $id The account id
+     * @param array $optParams Optional parameters.
+     * @return Google_Account
+     */
+    public function get($id, $optParams = array()) {
+      $params = array('id' => $id);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_Account($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "creatives" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adexchangebuyerService = new Google_AdexchangebuyerService(...);
+   *   $creatives = $adexchangebuyerService->creatives;
+   *  </code>
+   */
+  class Google_CreativesServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Submit a new creative. (creatives.insert)
+     *
+     * @param Google_Creative $postBody
+     * @param array $optParams Optional parameters.
+     * @return Google_Creative
+     */
+    public function insert(Google_Creative $postBody, $optParams = array()) {
+      $params = array('postBody' => $postBody);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('insert', array($params));
+      if ($this->useObjects()) {
+        return new Google_Creative($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Retrieves a list of the authenticated user's active creatives. (creatives.list)
+     *
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through ad clients. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response. Optional.
+     * @opt_param string maxResults Maximum number of entries returned on one result page. If not set, the default is 100. Optional.
+     * @return Google_CreativesList
+     */
+    public function listCreatives($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_CreativesList($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Gets the status for a single creative. (creatives.get)
+     *
+     * @param int $accountId The id for the account that will serve this creative.
+     * @param string $buyerCreativeId The buyer-specific id for this creative.
+     * @param string $adgroupId The adgroup this creative belongs to.
+     * @param array $optParams Optional parameters.
+     * @return Google_Creative
+     */
+    public function get($accountId, $buyerCreativeId, $adgroupId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'buyerCreativeId' => $buyerCreativeId, 'adgroupId' => $adgroupId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_Creative($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+/**
+ * Service definition for Google_Adexchangebuyer (v1).
+ *
+ * <p>
+ * Lets you manage your Ad Exchange Buyer account.
+ * </p>
+ *
+ * <p>
+ * For more information about this service, see the
+ * <a href="https://developers.google.com/ad-exchange/buyer-rest" target="_blank">API Documentation</a>
+ * </p>
+ *
+ * @author Google, Inc.
+ */
+class Google_AdexchangebuyerService extends Google_Service {
+  public $directDeals;
+  public $accounts;
+  public $creatives;
+  /**
+   * Constructs the internal representation of the Adexchangebuyer service.
+   *
+   * @param Google_Client $client
+   */
+  public function __construct(Google_Client $client) {
+    $this->servicePath = 'adexchangebuyer/v1/';
+    $this->version = 'v1';
+    $this->serviceName = 'adexchangebuyer';
+
+    $client->addService($this->serviceName, $this->version);
+    $this->directDeals = new Google_DirectDealsServiceResource($this, $this->serviceName, 'directDeals', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "path": "directdeals", "response": {"$ref": "DirectDealsList"}, "id": "adexchangebuyer.directDeals.list", "httpMethod": "GET"}, "get": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"id": {"required": true, "type": "string", "location": "path", "format": "int64"}}, "id": "adexchangebuyer.directDeals.get", "httpMethod": "GET", "path": "directdeals/{id}", "response": {"$ref": "DirectDeal"}}}}', true));
+    $this->accounts = new Google_AccountsServiceResource($this, $this->serviceName, 'accounts', json_decode('{"methods": {"patch": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"id": {"required": true, "type": "integer", "location": "path", "format": "int32"}}, "request": {"$ref": "Account"}, "response": {"$ref": "Account"}, "httpMethod": "PATCH", "path": "accounts/{id}", "id": "adexchangebuyer.accounts.patch"}, "list": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "path": "accounts", "response": {"$ref": "AccountsList"}, "id": "adexchangebuyer.accounts.list", "httpMethod": "GET"}, "update": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"id": {"required": true, "type": "integer", "location": "path", "format": "int32"}}, "request": {"$ref": "Account"}, "response": {"$ref": "Account"}, "httpMethod": "PUT", "path": "accounts/{id}", "id": "adexchangebuyer.accounts.update"}, "get": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"id": {"required": true, "type": "integer", "location": "path", "format": "int32"}}, "id": "adexchangebuyer.accounts.get", "httpMethod": "GET", "path": "accounts/{id}", "response": {"$ref": "Account"}}}}', true));
+    $this->creatives = new Google_CreativesServiceResource($this, $this->serviceName, 'creatives', json_decode('{"methods": {"insert": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "request": {"$ref": "Creative"}, "response": {"$ref": "Creative"}, "httpMethod": "POST", "path": "creatives", "id": "adexchangebuyer.creatives.insert"}, "list": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "1", "type": "integer", "maximum": "1000", "format": "uint32"}}, "response": {"$ref": "CreativesList"}, "httpMethod": "GET", "path": "creatives", "id": "adexchangebuyer.creatives.list"}, "get": {"scopes": ["https://www.googleapis.com/auth/adexchange.buyer"], "parameters": {"adgroupId": {"required": true, "type": "string", "location": "query", "format": "int64"}, "buyerCreativeId": {"required": true, "type": "string", "location": "path"}, "accountId": {"required": true, "type": "integer", "location": "path", "format": "int32"}}, "id": "adexchangebuyer.creatives.get", "httpMethod": "GET", "path": "creatives/{accountId}/{buyerCreativeId}", "response": {"$ref": "Creative"}}}}', true));
+
+  }
+}
+
+class Google_Account extends Google_Model {
+  public $kind;
+  public $maximumTotalQps;
+  protected $__bidderLocationType = 'Google_AccountBidderLocation';
+  protected $__bidderLocationDataType = 'array';
+  public $bidderLocation;
+  public $cookieMatchingNid;
+  public $id;
+  public $cookieMatchingUrl;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setMaximumTotalQps($maximumTotalQps) {
+    $this->maximumTotalQps = $maximumTotalQps;
+  }
+  public function getMaximumTotalQps() {
+    return $this->maximumTotalQps;
+  }
+  public function setBidderLocation($bidderLocation) {
+    $this->assertIsArray($bidderLocation, 'Google_AccountBidderLocation', __METHOD__);
+    $this->bidderLocation = $bidderLocation;
+  }
+  public function getBidderLocation() {
+    return $this->bidderLocation;
+  }
+  public function setCookieMatchingNid($cookieMatchingNid) {
+    $this->cookieMatchingNid = $cookieMatchingNid;
+  }
+  public function getCookieMatchingNid() {
+    return $this->cookieMatchingNid;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setCookieMatchingUrl($cookieMatchingUrl) {
+    $this->cookieMatchingUrl = $cookieMatchingUrl;
+  }
+  public function getCookieMatchingUrl() {
+    return $this->cookieMatchingUrl;
+  }
+}
+
+class Google_AccountBidderLocation extends Google_Model {
+  public $url;
+  public $maximumQps;
+  public function setUrl($url) {
+    $this->url = $url;
+  }
+  public function getUrl() {
+    return $this->url;
+  }
+  public function setMaximumQps($maximumQps) {
+    $this->maximumQps = $maximumQps;
+  }
+  public function getMaximumQps() {
+    return $this->maximumQps;
+  }
+}
+
+class Google_AccountsList extends Google_Model {
+  protected $__itemsType = 'Google_Account';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_Account', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+}
+
+class Google_Creative extends Google_Model {
+  public $productCategories;
+  public $advertiserName;
+  public $adgroupId;
+  public $videoURL;
+  public $width;
+  public $attribute;
+  public $kind;
+  public $height;
+  public $advertiserId;
+  public $HTMLSnippet;
+  public $status;
+  public $buyerCreativeId;
+  public $clickThroughUrl;
+  public $vendorType;
+  public $disapprovalReasons;
+  public $sensitiveCategories;
+  public $accountId;
+  public function setProductCategories($productCategories) {
+    $this->productCategories = $productCategories;
+  }
+  public function getProductCategories() {
+    return $this->productCategories;
+  }
+  public function setAdvertiserName($advertiserName) {
+    $this->advertiserName = $advertiserName;
+  }
+  public function getAdvertiserName() {
+    return $this->advertiserName;
+  }
+  public function setAdgroupId($adgroupId) {
+    $this->adgroupId = $adgroupId;
+  }
+  public function getAdgroupId() {
+    return $this->adgroupId;
+  }
+  public function setVideoURL($videoURL) {
+    $this->videoURL = $videoURL;
+  }
+  public function getVideoURL() {
+    return $this->videoURL;
+  }
+  public function setWidth($width) {
+    $this->width = $width;
+  }
+  public function getWidth() {
+    return $this->width;
+  }
+  public function setAttribute($attribute) {
+    $this->attribute = $attribute;
+  }
+  public function getAttribute() {
+    return $this->attribute;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setHeight($height) {
+    $this->height = $height;
+  }
+  public function getHeight() {
+    return $this->height;
+  }
+  public function setAdvertiserId($advertiserId) {
+    $this->advertiserId = $advertiserId;
+  }
+  public function getAdvertiserId() {
+    return $this->advertiserId;
+  }
+  public function setHTMLSnippet($HTMLSnippet) {
+    $this->HTMLSnippet = $HTMLSnippet;
+  }
+  public function getHTMLSnippet() {
+    return $this->HTMLSnippet;
+  }
+  public function setStatus($status) {
+    $this->status = $status;
+  }
+  public function getStatus() {
+    return $this->status;
+  }
+  public function setBuyerCreativeId($buyerCreativeId) {
+    $this->buyerCreativeId = $buyerCreativeId;
+  }
+  public function getBuyerCreativeId() {
+    return $this->buyerCreativeId;
+  }
+  public function setClickThroughUrl($clickThroughUrl) {
+    $this->clickThroughUrl = $clickThroughUrl;
+  }
+  public function getClickThroughUrl() {
+    return $this->clickThroughUrl;
+  }
+  public function setVendorType($vendorType) {
+    $this->vendorType = $vendorType;
+  }
+  public function getVendorType() {
+    return $this->vendorType;
+  }
+  public function setDisapprovalReasons($disapprovalReasons) {
+    $this->disapprovalReasons = $disapprovalReasons;
+  }
+  public function getDisapprovalReasons() {
+    return $this->disapprovalReasons;
+  }
+  public function setSensitiveCategories($sensitiveCategories) {
+    $this->sensitiveCategories = $sensitiveCategories;
+  }
+  public function getSensitiveCategories() {
+    return $this->sensitiveCategories;
+  }
+  public function setAccountId($accountId) {
+    $this->accountId = $accountId;
+  }
+  public function getAccountId() {
+    return $this->accountId;
+  }
+}
+
+class Google_CreativesList extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_Creative';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_Creative', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+}
+
+class Google_DirectDeal extends Google_Model {
+  public $advertiser;
+  public $kind;
+  public $currencyCode;
+  public $fixedCpm;
+  public $startTime;
+  public $endTime;
+  public $sellerNetwork;
+  public $id;
+  public $accountId;
+  public function setAdvertiser($advertiser) {
+    $this->advertiser = $advertiser;
+  }
+  public function getAdvertiser() {
+    return $this->advertiser;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setCurrencyCode($currencyCode) {
+    $this->currencyCode = $currencyCode;
+  }
+  public function getCurrencyCode() {
+    return $this->currencyCode;
+  }
+  public function setFixedCpm($fixedCpm) {
+    $this->fixedCpm = $fixedCpm;
+  }
+  public function getFixedCpm() {
+    return $this->fixedCpm;
+  }
+  public function setStartTime($startTime) {
+    $this->startTime = $startTime;
+  }
+  public function getStartTime() {
+    return $this->startTime;
+  }
+  public function setEndTime($endTime) {
+    $this->endTime = $endTime;
+  }
+  public function getEndTime() {
+    return $this->endTime;
+  }
+  public function setSellerNetwork($sellerNetwork) {
+    $this->sellerNetwork = $sellerNetwork;
+  }
+  public function getSellerNetwork() {
+    return $this->sellerNetwork;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setAccountId($accountId) {
+    $this->accountId = $accountId;
+  }
+  public function getAccountId() {
+    return $this->accountId;
+  }
+}
+
+class Google_DirectDealsList extends Google_Model {
+  public $kind;
+  protected $__directDealsType = 'Google_DirectDeal';
+  protected $__directDealsDataType = 'array';
+  public $directDeals;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setDirectDeals($directDeals) {
+    $this->assertIsArray($directDeals, 'Google_DirectDeal', __METHOD__);
+    $this->directDeals = $directDeals;
+  }
+  public function getDirectDeals() {
+    return $this->directDeals;
+  }
+}
diff --git a/lib/google/contrib/Google_AdsenseService.php b/lib/google/contrib/Google_AdsenseService.php
new file mode 100644 (file)
index 0000000..d887090
--- /dev/null
@@ -0,0 +1,1140 @@
+<?php
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+  /**
+   * The "urlchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $urlchannels = $adsenseService->urlchannels;
+   *  </code>
+   */
+  class Google_UrlchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all URL channels in the specified ad client for this AdSense account. (urlchannels.list)
+     *
+     * @param string $adClientId Ad client for which to list URL channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through URL channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of URL channels to include in the response, used for paging.
+     * @return Google_UrlChannels
+     */
+    public function listUrlchannels($adClientId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_UrlChannels($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adunits" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adunits = $adsenseService->adunits;
+   *  </code>
+   */
+  class Google_AdunitsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad units in the specified ad client for this AdSense account. (adunits.list)
+     *
+     * @param string $adClientId Ad client for which to list ad units.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param bool includeInactive Whether to include inactive ad units. Default: true.
+     * @opt_param string pageToken A continuation token, used to page through ad units. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of ad units to include in the response, used for paging.
+     * @return Google_AdUnits
+     */
+    public function listAdunits($adClientId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnits($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Gets the specified ad unit in the specified ad client. (adunits.get)
+     *
+     * @param string $adClientId Ad client for which to get the ad unit.
+     * @param string $adUnitId Ad unit to retrieve.
+     * @param array $optParams Optional parameters.
+     * @return Google_AdUnit
+     */
+    public function get($adClientId, $adUnitId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnit($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "customchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $customchannels = $adsenseService->customchannels;
+   *  </code>
+   */
+  class Google_AdunitsCustomchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all custom channels which the specified ad unit belongs to. (customchannels.list)
+     *
+     * @param string $adClientId Ad client which contains the ad unit.
+     * @param string $adUnitId Ad unit for which to list custom channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through custom channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of custom channels to include in the response, used for paging.
+     * @return Google_CustomChannels
+     */
+    public function listAdunitsCustomchannels($adClientId, $adUnitId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannels($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adclients" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adclients = $adsenseService->adclients;
+   *  </code>
+   */
+  class Google_AdclientsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad clients in this AdSense account. (adclients.list)
+     *
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through ad clients. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of ad clients to include in the response, used for paging.
+     * @return Google_AdClients
+     */
+    public function listAdclients($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdClients($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "reports" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $reports = $adsenseService->reports;
+   *  </code>
+   */
+  class Google_ReportsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Generate an AdSense report based on the report request sent in the query parameters. Returns the
+     * result as JSON; to retrieve output in CSV format specify "alt=csv" as a query parameter.
+     * (reports.generate)
+     *
+     * @param string $startDate Start of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param string $endDate End of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string sort The name of a dimension or metric to sort the resulting report on, optionally prefixed with "+" to sort ascending or "-" to sort descending. If no prefix is specified, the column is sorted ascending.
+     * @opt_param string locale Optional locale to use for translating report output to a local language. Defaults to "en_US" if not specified.
+     * @opt_param string metric Numeric columns to include in the report.
+     * @opt_param int maxResults The maximum number of rows of report data to return.
+     * @opt_param string filter Filters to be run on the report.
+     * @opt_param string currency Optional currency to use when reporting on monetary metrics. Defaults to the account's currency if not set.
+     * @opt_param int startIndex Index of the first row of report data to return.
+     * @opt_param string dimension Dimensions to base the report on.
+     * @opt_param string accountId Accounts upon which to report.
+     * @return Google_AdsenseReportsGenerateResponse
+     */
+    public function generate($startDate, $endDate, $optParams = array()) {
+      $params = array('startDate' => $startDate, 'endDate' => $endDate);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('generate', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdsenseReportsGenerateResponse($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "accounts" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $accounts = $adsenseService->accounts;
+   *  </code>
+   */
+  class Google_AccountsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all accounts available to this AdSense account. (accounts.list)
+     *
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through accounts. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of accounts to include in the response, used for paging.
+     * @return Google_Accounts
+     */
+    public function listAccounts($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_Accounts($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get information about the selected AdSense account. (accounts.get)
+     *
+     * @param string $accountId Account to get information about.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param bool tree Whether the tree of sub accounts should be returned.
+     * @return Google_Account
+     */
+    public function get($accountId, $optParams = array()) {
+      $params = array('accountId' => $accountId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_Account($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "urlchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $urlchannels = $adsenseService->urlchannels;
+   *  </code>
+   */
+  class Google_AccountsUrlchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all URL channels in the specified ad client for the specified account. (urlchannels.list)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client for which to list URL channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through URL channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of URL channels to include in the response, used for paging.
+     * @return Google_UrlChannels
+     */
+    public function listAccountsUrlchannels($accountId, $adClientId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_UrlChannels($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+  /**
+   * The "adunits" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adunits = $adsenseService->adunits;
+   *  </code>
+   */
+  class Google_AccountsAdunitsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad units in the specified ad client for the specified account. (adunits.list)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client for which to list ad units.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param bool includeInactive Whether to include inactive ad units. Default: true.
+     * @opt_param string pageToken A continuation token, used to page through ad units. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of ad units to include in the response, used for paging.
+     * @return Google_AdUnits
+     */
+    public function listAccountsAdunits($accountId, $adClientId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnits($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Gets the specified ad unit in the specified ad client for the specified account. (adunits.get)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client for which to get the ad unit.
+     * @param string $adUnitId Ad unit to retrieve.
+     * @param array $optParams Optional parameters.
+     * @return Google_AdUnit
+     */
+    public function get($accountId, $adClientId, $adUnitId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnit($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "customchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $customchannels = $adsenseService->customchannels;
+   *  </code>
+   */
+  class Google_AccountsAdunitsCustomchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all custom channels which the specified ad unit belongs to. (customchannels.list)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client which contains the ad unit.
+     * @param string $adUnitId Ad unit for which to list custom channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through custom channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of custom channels to include in the response, used for paging.
+     * @return Google_CustomChannels
+     */
+    public function listAccountsAdunitsCustomchannels($accountId, $adClientId, $adUnitId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannels($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+  /**
+   * The "adclients" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adclients = $adsenseService->adclients;
+   *  </code>
+   */
+  class Google_AccountsAdclientsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad clients in the specified account. (adclients.list)
+     *
+     * @param string $accountId Account for which to list ad clients.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through ad clients. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of ad clients to include in the response, used for paging.
+     * @return Google_AdClients
+     */
+    public function listAccountsAdclients($accountId, $optParams = array()) {
+      $params = array('accountId' => $accountId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdClients($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+  /**
+   * The "reports" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $reports = $adsenseService->reports;
+   *  </code>
+   */
+  class Google_AccountsReportsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Generate an AdSense report based on the report request sent in the query parameters. Returns the
+     * result as JSON; to retrieve output in CSV format specify "alt=csv" as a query parameter.
+     * (reports.generate)
+     *
+     * @param string $accountId Account upon which to report.
+     * @param string $startDate Start of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param string $endDate End of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string sort The name of a dimension or metric to sort the resulting report on, optionally prefixed with "+" to sort ascending or "-" to sort descending. If no prefix is specified, the column is sorted ascending.
+     * @opt_param string locale Optional locale to use for translating report output to a local language. Defaults to "en_US" if not specified.
+     * @opt_param string metric Numeric columns to include in the report.
+     * @opt_param int maxResults The maximum number of rows of report data to return.
+     * @opt_param string filter Filters to be run on the report.
+     * @opt_param string currency Optional currency to use when reporting on monetary metrics. Defaults to the account's currency if not set.
+     * @opt_param int startIndex Index of the first row of report data to return.
+     * @opt_param string dimension Dimensions to base the report on.
+     * @return Google_AdsenseReportsGenerateResponse
+     */
+    public function generate($accountId, $startDate, $endDate, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'startDate' => $startDate, 'endDate' => $endDate);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('generate', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdsenseReportsGenerateResponse($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+  /**
+   * The "customchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $customchannels = $adsenseService->customchannels;
+   *  </code>
+   */
+  class Google_AccountsCustomchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all custom channels in the specified ad client for the specified account.
+     * (customchannels.list)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client for which to list custom channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through custom channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of custom channels to include in the response, used for paging.
+     * @return Google_CustomChannels
+     */
+    public function listAccountsCustomchannels($accountId, $adClientId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannels($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get the specified custom channel from the specified ad client for the specified account.
+     * (customchannels.get)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client which contains the custom channel.
+     * @param string $customChannelId Custom channel to retrieve.
+     * @param array $optParams Optional parameters.
+     * @return Google_CustomChannel
+     */
+    public function get($accountId, $adClientId, $customChannelId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'customChannelId' => $customChannelId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannel($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adunits" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adunits = $adsenseService->adunits;
+   *  </code>
+   */
+  class Google_AccountsCustomchannelsAdunitsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad units in the specified custom channel. (adunits.list)
+     *
+     * @param string $accountId Account to which the ad client belongs.
+     * @param string $adClientId Ad client which contains the custom channel.
+     * @param string $customChannelId Custom channel for which to list ad units.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param bool includeInactive Whether to include inactive ad units. Default: true.
+     * @opt_param int maxResults The maximum number of ad units to include in the response, used for paging.
+     * @opt_param string pageToken A continuation token, used to page through ad units. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @return Google_AdUnits
+     */
+    public function listAccountsCustomchannelsAdunits($accountId, $adClientId, $customChannelId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'customChannelId' => $customChannelId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnits($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "customchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $customchannels = $adsenseService->customchannels;
+   *  </code>
+   */
+  class Google_CustomchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all custom channels in the specified ad client for this AdSense account.
+     * (customchannels.list)
+     *
+     * @param string $adClientId Ad client for which to list custom channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through custom channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of custom channels to include in the response, used for paging.
+     * @return Google_CustomChannels
+     */
+    public function listCustomchannels($adClientId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannels($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get the specified custom channel from the specified ad client. (customchannels.get)
+     *
+     * @param string $adClientId Ad client which contains the custom channel.
+     * @param string $customChannelId Custom channel to retrieve.
+     * @param array $optParams Optional parameters.
+     * @return Google_CustomChannel
+     */
+    public function get($adClientId, $customChannelId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'customChannelId' => $customChannelId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_CustomChannel($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adunits" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsenseService = new Google_AdsenseService(...);
+   *   $adunits = $adsenseService->adunits;
+   *  </code>
+   */
+  class Google_CustomchannelsAdunitsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all ad units in the specified custom channel. (adunits.list)
+     *
+     * @param string $adClientId Ad client which contains the custom channel.
+     * @param string $customChannelId Custom channel for which to list ad units.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param bool includeInactive Whether to include inactive ad units. Default: true.
+     * @opt_param string pageToken A continuation token, used to page through ad units. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param int maxResults The maximum number of ad units to include in the response, used for paging.
+     * @return Google_AdUnits
+     */
+    public function listCustomchannelsAdunits($adClientId, $customChannelId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'customChannelId' => $customChannelId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnits($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+/**
+ * Service definition for Google_Adsense (v1.1).
+ *
+ * <p>
+ * Gives AdSense publishers access to their inventory and the ability to generate reports
+ * </p>
+ *
+ * <p>
+ * For more information about this service, see the
+ * <a href="https://developers.google.com/adsense/management/" target="_blank">API Documentation</a>
+ * </p>
+ *
+ * @author Google, Inc.
+ */
+class Google_AdsenseService extends Google_Service {
+  public $urlchannels;
+  public $adunits;
+  public $adunits_customchannels;
+  public $adclients;
+  public $reports;
+  public $accounts;
+  public $accounts_urlchannels;
+  public $accounts_adunits;
+  public $accounts_adunits_customchannels;
+  public $accounts_adclients;
+  public $accounts_reports;
+  public $accounts_customchannels;
+  public $accounts_customchannels_adunits;
+  public $customchannels;
+  public $customchannels_adunits;
+  /**
+   * Constructs the internal representation of the Adsense service.
+   *
+   * @param Google_Client $client
+   */
+  public function __construct(Google_Client $client) {
+    $this->servicePath = 'adsense/v1.1/';
+    $this->version = 'v1.1';
+    $this->serviceName = 'adsense';
+
+    $client->addService($this->serviceName, $this->version);
+    $this->urlchannels = new Google_UrlchannelsServiceResource($this, $this->serviceName, 'urlchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "id": "adsense.urlchannels.list", "httpMethod": "GET", "path": "adclients/{adClientId}/urlchannels", "response": {"$ref": "UrlChannels"}}}}', true));
+    $this->adunits = new Google_AdunitsServiceResource($this, $this->serviceName, 'adunits', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"includeInactive": {"type": "boolean", "location": "query"}, "pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "id": "adsense.adunits.list", "httpMethod": "GET", "path": "adclients/{adClientId}/adunits", "response": {"$ref": "AdUnits"}}, "get": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"adClientId": {"required": true, "type": "string", "location": "path"}, "adUnitId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.adunits.get", "httpMethod": "GET", "path": "adclients/{adClientId}/adunits/{adUnitId}", "response": {"$ref": "AdUnit"}}}}', true));
+    $this->adunits_customchannels = new Google_AdunitsCustomchannelsServiceResource($this, $this->serviceName, 'customchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "adUnitId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.adunits.customchannels.list", "httpMethod": "GET", "path": "adclients/{adClientId}/adunits/{adUnitId}/customchannels", "response": {"$ref": "CustomChannels"}}}}', true));
+    $this->adclients = new Google_AdclientsServiceResource($this, $this->serviceName, 'adclients', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "response": {"$ref": "AdClients"}, "httpMethod": "GET", "path": "adclients", "id": "adsense.adclients.list"}}}', true));
+    $this->reports = new Google_ReportsServiceResource($this, $this->serviceName, 'reports', json_decode('{"methods": {"generate": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"sort": {"repeated": true, "type": "string", "location": "query"}, "startDate": {"required": true, "type": "string", "location": "query"}, "endDate": {"required": true, "type": "string", "location": "query"}, "locale": {"type": "string", "location": "query"}, "metric": {"repeated": true, "type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "50000", "format": "int32"}, "filter": {"repeated": true, "type": "string", "location": "query"}, "currency": {"type": "string", "location": "query"}, "startIndex": {"location": "query", "minimum": "0", "type": "integer", "maximum": "5000", "format": "int32"}, "dimension": {"repeated": true, "type": "string", "location": "query"}, "accountId": {"repeated": true, "type": "string", "location": "query"}}, "id": "adsense.reports.generate", "httpMethod": "GET", "supportsMediaDownload": true, "path": "reports", "response": {"$ref": "AdsenseReportsGenerateResponse"}}}}', true));
+    $this->accounts = new Google_AccountsServiceResource($this, $this->serviceName, 'accounts', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "response": {"$ref": "Accounts"}, "httpMethod": "GET", "path": "accounts", "id": "adsense.accounts.list"}, "get": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"tree": {"type": "boolean", "location": "query"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.get", "httpMethod": "GET", "path": "accounts/{accountId}", "response": {"$ref": "Account"}}}}', true));
+    $this->accounts_urlchannels = new Google_AccountsUrlchannelsServiceResource($this, $this->serviceName, 'urlchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.urlchannels.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/urlchannels", "response": {"$ref": "UrlChannels"}}}}', true));
+    $this->accounts_adunits = new Google_AccountsAdunitsServiceResource($this, $this->serviceName, 'adunits', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"includeInactive": {"type": "boolean", "location": "query"}, "pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.adunits.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/adunits", "response": {"$ref": "AdUnits"}}, "get": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"adClientId": {"required": true, "type": "string", "location": "path"}, "adUnitId": {"required": true, "type": "string", "location": "path"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.adunits.get", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/adunits/{adUnitId}", "response": {"$ref": "AdUnit"}}}}', true));
+    $this->accounts_adunits_customchannels = new Google_AccountsAdunitsCustomchannelsServiceResource($this, $this->serviceName, 'customchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "adUnitId": {"required": true, "type": "string", "location": "path"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.adunits.customchannels.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/adunits/{adUnitId}/customchannels", "response": {"$ref": "CustomChannels"}}}}', true));
+    $this->accounts_adclients = new Google_AccountsAdclientsServiceResource($this, $this->serviceName, 'adclients', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.adclients.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients", "response": {"$ref": "AdClients"}}}}', true));
+    $this->accounts_reports = new Google_AccountsReportsServiceResource($this, $this->serviceName, 'reports', json_decode('{"methods": {"generate": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"sort": {"repeated": true, "type": "string", "location": "query"}, "startDate": {"required": true, "type": "string", "location": "query"}, "endDate": {"required": true, "type": "string", "location": "query"}, "locale": {"type": "string", "location": "query"}, "metric": {"repeated": true, "type": "string", "location": "query"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "50000", "format": "int32"}, "filter": {"repeated": true, "type": "string", "location": "query"}, "currency": {"type": "string", "location": "query"}, "startIndex": {"location": "query", "minimum": "0", "type": "integer", "maximum": "5000", "format": "int32"}, "dimension": {"repeated": true, "type": "string", "location": "query"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.reports.generate", "httpMethod": "GET", "supportsMediaDownload": true, "path": "accounts/{accountId}/reports", "response": {"$ref": "AdsenseReportsGenerateResponse"}}}}', true));
+    $this->accounts_customchannels = new Google_AccountsCustomchannelsServiceResource($this, $this->serviceName, 'customchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.customchannels.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/customchannels", "response": {"$ref": "CustomChannels"}}, "get": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"customChannelId": {"required": true, "type": "string", "location": "path"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.customchannels.get", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/customchannels/{customChannelId}", "response": {"$ref": "CustomChannel"}}}}', true));
+    $this->accounts_customchannels_adunits = new Google_AccountsCustomchannelsAdunitsServiceResource($this, $this->serviceName, 'adunits', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"includeInactive": {"type": "boolean", "location": "query"}, "customChannelId": {"required": true, "type": "string", "location": "path"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}, "pageToken": {"type": "string", "location": "query"}, "accountId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.accounts.customchannels.adunits.list", "httpMethod": "GET", "path": "accounts/{accountId}/adclients/{adClientId}/customchannels/{customChannelId}/adunits", "response": {"$ref": "AdUnits"}}}}', true));
+    $this->customchannels = new Google_CustomchannelsServiceResource($this, $this->serviceName, 'customchannels', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"pageToken": {"type": "string", "location": "query"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "id": "adsense.customchannels.list", "httpMethod": "GET", "path": "adclients/{adClientId}/customchannels", "response": {"$ref": "CustomChannels"}}, "get": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"customChannelId": {"required": true, "type": "string", "location": "path"}, "adClientId": {"required": true, "type": "string", "location": "path"}}, "id": "adsense.customchannels.get", "httpMethod": "GET", "path": "adclients/{adClientId}/customchannels/{customChannelId}", "response": {"$ref": "CustomChannel"}}}}', true));
+    $this->customchannels_adunits = new Google_CustomchannelsAdunitsServiceResource($this, $this->serviceName, 'adunits', json_decode('{"methods": {"list": {"scopes": ["https://www.googleapis.com/auth/adsense", "https://www.googleapis.com/auth/adsense.readonly"], "parameters": {"includeInactive": {"type": "boolean", "location": "query"}, "pageToken": {"type": "string", "location": "query"}, "customChannelId": {"required": true, "type": "string", "location": "path"}, "adClientId": {"required": true, "type": "string", "location": "path"}, "maxResults": {"location": "query", "minimum": "0", "type": "integer", "maximum": "10000", "format": "int32"}}, "id": "adsense.customchannels.adunits.list", "httpMethod": "GET", "path": "adclients/{adClientId}/customchannels/{customChannelId}/adunits", "response": {"$ref": "AdUnits"}}}}', true));
+
+  }
+}
+
+class Google_Account extends Google_Model {
+  public $kind;
+  public $id;
+  protected $__subAccountsType = 'Google_Account';
+  protected $__subAccountsDataType = 'array';
+  public $subAccounts;
+  public $name;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setSubAccounts($subAccounts) {
+    $this->assertIsArray($subAccounts, 'Google_Account', __METHOD__);
+    $this->subAccounts = $subAccounts;
+  }
+  public function getSubAccounts() {
+    return $this->subAccounts;
+  }
+  public function setName($name) {
+    $this->name = $name;
+  }
+  public function getName() {
+    return $this->name;
+  }
+}
+
+class Google_Accounts extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_Account';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public $etag;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_Account', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setEtag($etag) {
+    $this->etag = $etag;
+  }
+  public function getEtag() {
+    return $this->etag;
+  }
+}
+
+class Google_AdClient extends Google_Model {
+  public $productCode;
+  public $kind;
+  public $id;
+  public $supportsReporting;
+  public function setProductCode($productCode) {
+    $this->productCode = $productCode;
+  }
+  public function getProductCode() {
+    return $this->productCode;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setSupportsReporting($supportsReporting) {
+    $this->supportsReporting = $supportsReporting;
+  }
+  public function getSupportsReporting() {
+    return $this->supportsReporting;
+  }
+}
+
+class Google_AdClients extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_AdClient';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public $etag;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_AdClient', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setEtag($etag) {
+    $this->etag = $etag;
+  }
+  public function getEtag() {
+    return $this->etag;
+  }
+}
+
+class Google_AdUnit extends Google_Model {
+  public $status;
+  public $kind;
+  public $code;
+  public $id;
+  public $name;
+  public function setStatus($status) {
+    $this->status = $status;
+  }
+  public function getStatus() {
+    return $this->status;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setCode($code) {
+    $this->code = $code;
+  }
+  public function getCode() {
+    return $this->code;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setName($name) {
+    $this->name = $name;
+  }
+  public function getName() {
+    return $this->name;
+  }
+}
+
+class Google_AdUnits extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_AdUnit';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public $etag;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_AdUnit', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setEtag($etag) {
+    $this->etag = $etag;
+  }
+  public function getEtag() {
+    return $this->etag;
+  }
+}
+
+class Google_AdsenseReportsGenerateResponse extends Google_Model {
+  public $kind;
+  public $rows;
+  public $warnings;
+  public $totals;
+  protected $__headersType = 'Google_AdsenseReportsGenerateResponseHeaders';
+  protected $__headersDataType = 'array';
+  public $headers;
+  public $totalMatchedRows;
+  public $averages;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setRows($rows) {
+    $this->rows = $rows;
+  }
+  public function getRows() {
+    return $this->rows;
+  }
+  public function setWarnings($warnings) {
+    $this->warnings = $warnings;
+  }
+  public function getWarnings() {
+    return $this->warnings;
+  }
+  public function setTotals($totals) {
+    $this->totals = $totals;
+  }
+  public function getTotals() {
+    return $this->totals;
+  }
+  public function setHeaders($headers) {
+    $this->assertIsArray($headers, 'Google_AdsenseReportsGenerateResponseHeaders', __METHOD__);
+    $this->headers = $headers;
+  }
+  public function getHeaders() {
+    return $this->headers;
+  }
+  public function setTotalMatchedRows($totalMatchedRows) {
+    $this->totalMatchedRows = $totalMatchedRows;
+  }
+  public function getTotalMatchedRows() {
+    return $this->totalMatchedRows;
+  }
+  public function setAverages($averages) {
+    $this->averages = $averages;
+  }
+  public function getAverages() {
+    return $this->averages;
+  }
+}
+
+class Google_AdsenseReportsGenerateResponseHeaders extends Google_Model {
+  public $currency;
+  public $type;
+  public $name;
+  public function setCurrency($currency) {
+    $this->currency = $currency;
+  }
+  public function getCurrency() {
+    return $this->currency;
+  }
+  public function setType($type) {
+    $this->type = $type;
+  }
+  public function getType() {
+    return $this->type;
+  }
+  public function setName($name) {
+    $this->name = $name;
+  }
+  public function getName() {
+    return $this->name;
+  }
+}
+
+class Google_CustomChannel extends Google_Model {
+  public $kind;
+  public $code;
+  protected $__targetingInfoType = 'Google_CustomChannelTargetingInfo';
+  protected $__targetingInfoDataType = '';
+  public $targetingInfo;
+  public $id;
+  public $name;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setCode($code) {
+    $this->code = $code;
+  }
+  public function getCode() {
+    return $this->code;
+  }
+  public function setTargetingInfo(Google_CustomChannelTargetingInfo $targetingInfo) {
+    $this->targetingInfo = $targetingInfo;
+  }
+  public function getTargetingInfo() {
+    return $this->targetingInfo;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setName($name) {
+    $this->name = $name;
+  }
+  public function getName() {
+    return $this->name;
+  }
+}
+
+class Google_CustomChannelTargetingInfo extends Google_Model {
+  public $location;
+  public $adsAppearOn;
+  public $siteLanguage;
+  public $description;
+  public function setLocation($location) {
+    $this->location = $location;
+  }
+  public function getLocation() {
+    return $this->location;
+  }
+  public function setAdsAppearOn($adsAppearOn) {
+    $this->adsAppearOn = $adsAppearOn;
+  }
+  public function getAdsAppearOn() {
+    return $this->adsAppearOn;
+  }
+  public function setSiteLanguage($siteLanguage) {
+    $this->siteLanguage = $siteLanguage;
+  }
+  public function getSiteLanguage() {
+    return $this->siteLanguage;
+  }
+  public function setDescription($description) {
+    $this->description = $description;
+  }
+  public function getDescription() {
+    return $this->description;
+  }
+}
+
+class Google_CustomChannels extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_CustomChannel';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public $etag;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_CustomChannel', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setEtag($etag) {
+    $this->etag = $etag;
+  }
+  public function getEtag() {
+    return $this->etag;
+  }
+}
+
+class Google_UrlChannel extends Google_Model {
+  public $kind;
+  public $id;
+  public $urlPattern;
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setId($id) {
+    $this->id = $id;
+  }
+  public function getId() {
+    return $this->id;
+  }
+  public function setUrlPattern($urlPattern) {
+    $this->urlPattern = $urlPattern;
+  }
+  public function getUrlPattern() {
+    return $this->urlPattern;
+  }
+}
+
+class Google_UrlChannels extends Google_Model {
+  public $nextPageToken;
+  protected $__itemsType = 'Google_UrlChannel';
+  protected $__itemsDataType = 'array';
+  public $items;
+  public $kind;
+  public $etag;
+  public function setNextPageToken($nextPageToken) {
+    $this->nextPageToken = $nextPageToken;
+  }
+  public function getNextPageToken() {
+    return $this->nextPageToken;
+  }
+  public function setItems($items) {
+    $this->assertIsArray($items, 'Google_UrlChannel', __METHOD__);
+    $this->items = $items;
+  }
+  public function getItems() {
+    return $this->items;
+  }
+  public function setKind($kind) {
+    $this->kind = $kind;
+  }
+  public function getKind() {
+    return $this->kind;
+  }
+  public function setEtag($etag) {
+    $this->etag = $etag;
+  }
+  public function getEtag() {
+    return $this->etag;
+  }
+}
diff --git a/lib/google/contrib/Google_AdsensehostService.php b/lib/google/contrib/Google_AdsensehostService.php
new file mode 100644 (file)
index 0000000..246d052
--- /dev/null
@@ -0,0 +1,1378 @@
+<?php
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+
+  /**
+   * The "urlchannels" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $urlchannels = $adsensehostService->urlchannels;
+   *  </code>
+   */
+  class Google_UrlchannelsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Add a new URL channel to the host AdSense account. (urlchannels.insert)
+     *
+     * @param string $adClientId Ad client to which the new URL channel will be added.
+     * @param Google_UrlChannel $postBody
+     * @param array $optParams Optional parameters.
+     * @return Google_UrlChannel
+     */
+    public function insert($adClientId, Google_UrlChannel $postBody, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'postBody' => $postBody);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('insert', array($params));
+      if ($this->useObjects()) {
+        return new Google_UrlChannel($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * List all host URL channels in the host AdSense account. (urlchannels.list)
+     *
+     * @param string $adClientId Ad client for which to list URL channels.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through URL channels. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param string maxResults The maximum number of URL channels to include in the response, used for paging.
+     * @return Google_UrlChannels
+     */
+    public function listUrlchannels($adClientId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_UrlChannels($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Delete a URL channel from the host AdSense account. (urlchannels.delete)
+     *
+     * @param string $adClientId Ad client from which to delete the URL channel.
+     * @param string $urlChannelId URL channel to delete.
+     * @param array $optParams Optional parameters.
+     * @return Google_UrlChannel
+     */
+    public function delete($adClientId, $urlChannelId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId, 'urlChannelId' => $urlChannelId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('delete', array($params));
+      if ($this->useObjects()) {
+        return new Google_UrlChannel($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adclients" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $adclients = $adsensehostService->adclients;
+   *  </code>
+   */
+  class Google_AdclientsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List all host ad clients in this AdSense account. (adclients.list)
+     *
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string pageToken A continuation token, used to page through ad clients. To retrieve the next page, set this parameter to the value of "nextPageToken" from the previous response.
+     * @opt_param string maxResults The maximum number of ad clients to include in the response, used for paging.
+     * @return Google_AdClients
+     */
+    public function listAdclients($optParams = array()) {
+      $params = array();
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdClients($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get information about one of the ad clients in the Host AdSense account. (adclients.get)
+     *
+     * @param string $adClientId Ad client to get.
+     * @param array $optParams Optional parameters.
+     * @return Google_AdClient
+     */
+    public function get($adClientId, $optParams = array()) {
+      $params = array('adClientId' => $adClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdClient($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "associationsessions" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $associationsessions = $adsensehostService->associationsessions;
+   *  </code>
+   */
+  class Google_AssociationsessionsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Create an association session for initiating an association with an AdSense user.
+     * (associationsessions.start)
+     *
+     * @param string $productCode Products to associate with the user.
+     * @param string $websiteUrl The URL of the user's hosted website.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string websiteLocale The locale of the user's hosted website.
+     * @opt_param string userLocale The preferred locale of the user.
+     * @return Google_AssociationSession
+     */
+    public function start($productCode, $websiteUrl, $optParams = array()) {
+      $params = array('productCode' => $productCode, 'websiteUrl' => $websiteUrl);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('start', array($params));
+      if ($this->useObjects()) {
+        return new Google_AssociationSession($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Verify an association session after the association callback returns from AdSense signup.
+     * (associationsessions.verify)
+     *
+     * @param string $token The token returned to the association callback URL.
+     * @param array $optParams Optional parameters.
+     * @return Google_AssociationSession
+     */
+    public function verify($token, $optParams = array()) {
+      $params = array('token' => $token);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('verify', array($params));
+      if ($this->useObjects()) {
+        return new Google_AssociationSession($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "reports" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $reports = $adsensehostService->reports;
+   *  </code>
+   */
+  class Google_ReportsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Generate an AdSense report based on the report request sent in the query parameters. Returns the
+     * result as JSON; to retrieve output in CSV format specify "alt=csv" as a query parameter.
+     * (reports.generate)
+     *
+     * @param string $endDate End of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param string $startDate Start of the date range to report on in "YYYY-MM-DD" format, inclusive.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string sort The name of a dimension or metric to sort the resulting report on, optionally prefixed with "+" to sort ascending or "-" to sort descending. If no prefix is specified, the column is sorted ascending.
+     * @opt_param string locale Optional locale to use for translating report output to a local language. Defaults to "en_US" if not specified.
+     * @opt_param string metric Numeric columns to include in the report.
+     * @opt_param string maxResults The maximum number of rows of report data to return.
+     * @opt_param string filter Filters to be run on the report.
+     * @opt_param string startIndex Index of the first row of report data to return.
+     * @opt_param string dimension Dimensions to base the report on.
+     * @return Google_Report
+     */
+    public function generate($endDate, $startDate, $optParams = array()) {
+      $params = array('endDate' => $endDate, 'startDate' => $startDate);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('generate', array($params));
+      if ($this->useObjects()) {
+        return new Google_Report($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "accounts" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $accounts = $adsensehostService->accounts;
+   *  </code>
+   */
+  class Google_AccountsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * List hosted accounts associated with this AdSense account by ad client id. (accounts.list)
+     *
+     * @param string $filterAdClientId Ad clients to list accounts for.
+     * @param array $optParams Optional parameters.
+     * @return Google_Accounts
+     */
+    public function listAccounts($filterAdClientId, $optParams = array()) {
+      $params = array('filterAdClientId' => $filterAdClientId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('list', array($params));
+      if ($this->useObjects()) {
+        return new Google_Accounts($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get information about the selected associated AdSense account. (accounts.get)
+     *
+     * @param string $accountId Account to get information about.
+     * @param array $optParams Optional parameters.
+     * @return Google_Account
+     */
+    public function get($accountId, $optParams = array()) {
+      $params = array('accountId' => $accountId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_Account($data);
+      } else {
+        return $data;
+      }
+    }
+  }
+
+  /**
+   * The "adunits" collection of methods.
+   * Typical usage is:
+   *  <code>
+   *   $adsensehostService = new Google_AdsensehostService(...);
+   *   $adunits = $adsensehostService->adunits;
+   *  </code>
+   */
+  class Google_AccountsAdunitsServiceResource extends Google_ServiceResource {
+
+
+    /**
+     * Insert the supplied ad unit into the specified publisher AdSense account. (adunits.insert)
+     *
+     * @param string $accountId Account which will contain the ad unit.
+     * @param string $adClientId Ad client into which to insert the ad unit.
+     * @param Google_AdUnit $postBody
+     * @param array $optParams Optional parameters.
+     * @return Google_AdUnit
+     */
+    public function insert($accountId, $adClientId, Google_AdUnit $postBody, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'postBody' => $postBody);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('insert', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnit($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get the specified host ad unit in this AdSense account. (adunits.get)
+     *
+     * @param string $accountId Account which contains the ad unit.
+     * @param string $adClientId Ad client for which to get ad unit.
+     * @param string $adUnitId Ad unit to get.
+     * @param array $optParams Optional parameters.
+     * @return Google_AdUnit
+     */
+    public function get($accountId, $adClientId, $adUnitId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('get', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdUnit($data);
+      } else {
+        return $data;
+      }
+    }
+    /**
+     * Get ad code for the specified ad unit, attaching the specified host custom channels.
+     * (adunits.getAdCode)
+     *
+     * @param string $accountId Account which contains the ad client.
+     * @param string $adClientId Ad client with contains the ad unit.
+     * @param string $adUnitId Ad unit to get the code for.
+     * @param array $optParams Optional parameters.
+     *
+     * @opt_param string hostCustomChannelId Host custom channel to attach to the ad code.
+     * @return Google_AdCode
+     */
+    public function getAdCode($accountId, $adClientId, $adUnitId, $optParams = array()) {
+      $params = array('accountId' => $accountId, 'adClientId' => $adClientId, 'adUnitId' => $adUnitId);
+      $params = array_merge($params, $optParams);
+      $data = $this->__call('getAdCode', array($params));
+      if ($this->useObjects()) {
+        return new Google_AdCode($data);
+      } else {
+        return $data;
+      }
+    }</