瀏覽代碼

[DOCUMENTATION] English:

- fixed title tags

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@15234 44c647ce-9c0f-0410-b52a-842ac1e357ba
thomas 16 年之前
當前提交
e443b8c12a
共有 100 個文件被更改,包括 16733 次插入0 次删除
  1. 83 0
      INSTALL.txt
  2. 27 0
      LICENSE.txt
  3. 96 0
      README.txt
  4. 44 0
      bin/zf.bat
  5. 78 0
      bin/zf.php
  6. 44 0
      bin/zf.sh
  7. 39 0
      demos/Zend/Feeds/consume-feed.php
  8. 373 0
      demos/Zend/Gdata/Blogger.php
  9. 136 0
      demos/Zend/Gdata/BooksBrowser/books_browser.css
  10. 155 0
      demos/Zend/Gdata/BooksBrowser/index.php
  11. 81 0
      demos/Zend/Gdata/BooksBrowser/interface.html
  12. 841 0
      demos/Zend/Gdata/Calendar.php
  13. 940 0
      demos/Zend/Gdata/Docs.php
  14. 1992 0
      demos/Zend/Gdata/Gapps.php
  15. 670 0
      demos/Zend/Gdata/Gbase.php
  16. 386 0
      demos/Zend/Gdata/Health.php
  17. 386 0
      demos/Zend/Gdata/InstallationChecker.php
  18. 226 0
      demos/Zend/Gdata/MyLibrary/demo.php
  19. 904 0
      demos/Zend/Gdata/Photos.php
  20. 454 0
      demos/Zend/Gdata/Spreadsheet-ClientLogin.php
  21. 44 0
      demos/Zend/Gdata/YouTubeVideoApp/README.txt
  22. 193 0
      demos/Zend/Gdata/YouTubeVideoApp/index.php
  23. 二進制
      demos/Zend/Gdata/YouTubeVideoApp/notfound.jpg
  24. 1097 0
      demos/Zend/Gdata/YouTubeVideoApp/operations.php
  25. 66 0
      demos/Zend/Gdata/YouTubeVideoApp/session_details.php
  26. 236 0
      demos/Zend/Gdata/YouTubeVideoApp/video_app.css
  27. 582 0
      demos/Zend/Gdata/YouTubeVideoApp/video_app.js
  28. 278 0
      demos/Zend/Gdata/YouTubeVideoBrowser/index.php
  29. 79 0
      demos/Zend/Gdata/YouTubeVideoBrowser/interface.html
  30. 152 0
      demos/Zend/Gdata/YouTubeVideoBrowser/video_browser.css
  31. 228 0
      demos/Zend/Gdata/YouTubeVideoBrowser/video_browser.js
  32. 43 0
      demos/Zend/Locale/AllLanguages.php
  33. 449 0
      demos/Zend/Mail/SimpleMailer.php
  34. 二進制
      demos/Zend/Mail/maildir/maildir.tar
  35. 100 0
      demos/Zend/Mail/mbox/INBOX
  36. 14 0
      demos/Zend/Mail/mbox/subfolder/test
  37. 二進制
      demos/Zend/OpenId/login-bg.gif
  38. 64 0
      demos/Zend/OpenId/mvc_auth/application/controllers/ErrorController.php
  39. 112 0
      demos/Zend/OpenId/mvc_auth/application/controllers/IndexController.php
  40. 12 0
      demos/Zend/OpenId/mvc_auth/application/views/scripts/error/404.phtml
  41. 12 0
      demos/Zend/OpenId/mvc_auth/application/views/scripts/error/500.phtml
  42. 17 0
      demos/Zend/OpenId/mvc_auth/application/views/scripts/index/login.phtml
  43. 14 0
      demos/Zend/OpenId/mvc_auth/application/views/scripts/index/welcome.phtml
  44. 2 0
      demos/Zend/OpenId/mvc_auth/html/.htaccess
  45. 1 0
      demos/Zend/OpenId/mvc_auth/html/config.ini
  46. 36 0
      demos/Zend/OpenId/mvc_auth/html/index.php
  47. 9 0
      demos/Zend/OpenId/templates/identity.phtml
  48. 10 0
      demos/Zend/OpenId/templates/identity2.phtml
  49. 83 0
      demos/Zend/OpenId/templates/login.phtml
  50. 14 0
      demos/Zend/OpenId/templates/profile.phtml
  51. 69 0
      demos/Zend/OpenId/templates/register.phtml
  52. 11 0
      demos/Zend/OpenId/templates/registration_complete.phtml
  53. 18 0
      demos/Zend/OpenId/templates/trust.phtml
  54. 68 0
      demos/Zend/OpenId/test_auth.php
  55. 128 0
      demos/Zend/OpenId/test_consumer.php
  56. 268 0
      demos/Zend/OpenId/test_server.php
  57. 262 0
      demos/Zend/Pdf/demo.php
  58. 二進制
      demos/Zend/Pdf/stamp.jpg
  59. 47 0
      demos/Zend/Pdf/test.pdf
  60. 165 0
      demos/Zend/ProgressBar/JsPush.php
  61. 218 0
      demos/Zend/ProgressBar/Upload.php
  62. 225 0
      demos/Zend/ProgressBar/ZendForm.php
  63. 二進制
      demos/Zend/ProgressBar/animation.gif
  64. 57 0
      demos/Zend/Search/Lucene/feed-search/create-index.php
  65. 43 0
      demos/Zend/Search/Lucene/feed-search/search-index.php
  66. 97 0
      demos/Zend/Search/Lucene/indexing/CreateIndex.php
  67. 179 0
      demos/Zend/Search/Lucene/indexing/IndexSource/about-pear.html
  68. 235 0
      demos/Zend/Search/Lucene/indexing/IndexSource/authors.html
  69. 185 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.bugs.html
  70. 165 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.documentation.html
  71. 201 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.html
  72. 151 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.newpackage.html
  73. 274 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.patches.html
  74. 158 0
      demos/Zend/Search/Lucene/indexing/IndexSource/contributing.wishlist.html
  75. 196 0
      demos/Zend/Search/Lucene/indexing/IndexSource/copyright.html
  76. 159 0
      demos/Zend/Search/Lucene/indexing/IndexSource/core.html
  77. 214 0
      demos/Zend/WebServices/Amazon/amazon-search.php
  78. 162 0
      demos/Zend/WebServices/Flickr/flickr-composite.php
  79. 41 0
      demos/Zend/WebServices/Flickr/flickr-search.php
  80. 31 0
      demos/Zend/WebServices/Protocols/xmlrpc-upc-lookup.php
  81. 153 0
      demos/Zend/WebServices/Yahoo/yahoo-multi-search.php
  82. 18 0
      demos/Zend/Wildfire/application/controllers/Boot/Zend-Db-Profiler-Firebug/IndexController.php
  83. 13 0
      demos/Zend/Wildfire/application/controllers/Boot/Zend-Log-Writer-Firebug/IndexController.php
  84. 51 0
      demos/Zend/Wildfire/application/controllers/ErrorController.php
  85. 38 0
      demos/Zend/Wildfire/application/controllers/IndexController.php
  86. 91 0
      demos/Zend/Wildfire/application/controllers/ZendDbProfilerFirebugController.php
  87. 79 0
      demos/Zend/Wildfire/application/controllers/ZendLogWriterFirebugController.php
  88. 44 0
      demos/Zend/Wildfire/application/controllers/ZendWildfirePluginFirePhpController.php
  89. 16 0
      demos/Zend/Wildfire/application/views/scripts/error/error.phtml
  90. 57 0
      demos/Zend/Wildfire/application/views/scripts/index/index.phtml
  91. 16 0
      demos/Zend/Wildfire/application/views/scripts/zend-db-profiler-firebug/test-logging.phtml
  92. 16 0
      demos/Zend/Wildfire/application/views/scripts/zend-db-profiler-firebug/test-multiple-databases.phtml
  93. 0 0
      demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-error-controller.phtml
  94. 16 0
      demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-large-message.phtml
  95. 16 0
      demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-logging.phtml
  96. 16 0
      demos/Zend/Wildfire/application/views/scripts/zend-wildfire-plugin-firephp/test-groups.phtml
  97. 6 0
      demos/Zend/Wildfire/public/.htaccess
  98. 51 0
      demos/Zend/Wildfire/public/Boot/Zend-Db-Profiler-Firebug/TestDocExample.php
  99. 41 0
      demos/Zend/Wildfire/public/Boot/Zend-Log-Writer-Firebug/TestDocExample.php
  100. 66 0
      demos/Zend/Wildfire/public/index.php

+ 83 - 0
INSTALL.txt

@@ -0,0 +1,83 @@
+INSTALLATION
+------------
+
+Zend Framework requires no special installation steps. Simply download the framework,
+extract it to the folder you would like to keep it in, and add the library directory
+to your PHP include_path. To use components in the extras library, add the extras/library
+directory to your PHP include_path, as well.
+If you would like to use Zend_Tool, simply add bin/zf.bat (for Windows) or
+bin/zf.sh (for anything else) to your system executable path.
+
+SYSTEM REQUIREMENTS
+-------------------
+
+Zend Framework requires PHP 5.2.4 or later. Please see the system requirements
+appendix for more detailed information:
+
+http://framework.zend.com/manual/en/requirements.html
+
+DEVELOPMENT VERSIONS
+--------------------
+
+If you would like to preview enhancements or bug fixes that have not yet been
+released, you can obtain the current development version of Zend Framework using one
+of the following methods:
+
+* Download the latest nightly snapshot. For those who care to brave the cutting
+  (often bleeding) edge, the nightly snapshots represent the latest single-
+  download development version of Zend Framework development. Snapshots are bundled
+  with documentation in English only or in all available languages. If you anticipate
+  updating to the latest development version of Zend Framework often, consider using
+  Subversion as described below.
+
+  http://framework.zend.com/download/snapshot
+
+* Using a Subversion (SVN) client. Zend Framework is open source software, and
+  the Subversion repository used for its development is publicly available. Consider
+  using SVN to get Zend Framework if you already use SVN for your application
+  development, want to contribute back to the framework, or need to upgrade your
+  framework version very often.
+
+  Exporting is useful if you want to get a particular framework revision without the
+  .svn  directories as created in a working copy.
+
+  Checking out a working copy is necessary if you would like to directly contribute
+  to Zend Framework; a working copy can be updated any time with svn update.
+
+  An externals definition is highly convenient for developers already using SVN to
+  manage their application working copies.
+
+  The URL for the trunk of the Zend Framework SVN repository is:
+
+  http://framework.zend.com/svn/framework/trunk
+
+  For more information about Subversion, please see the official website:
+
+  http://subversion.tigris.org
+
+CONFIGURING THE INCLUDE PATH
+----------------------------
+
+Once you have a copy of Zend Framework available, your application will need to
+access the framework classes. Though there are several ways to achieve this, your
+PHP include_path needs to contain the path to the Zend Framework classes under the
+/library directory in this distribution. You can find out more about the PHP
+include_path configuration directive here:
+
+http://www.php.net/manual/en/ini.core.php#ini.include-path
+
+Instructions on how to change PHP configuration directives can be found here:
+
+http://www.php.net/manual/en/configuration.changes.php
+
+GETTING STARTED
+---------------
+
+A great place to get up-to-speed quickly is the Zend Framework QuickStart:
+
+http://framework.zend.com/docs/quickstart
+
+The QuickStart covers some of the most commonly used components of ZF. Since
+Zend Framework is designed with a use-at-will architecture and components are
+loosely coupled, you can select and use only those components that are needed for
+your project.

+ 27 - 0
LICENSE.txt

@@ -0,0 +1,27 @@
+Copyright (c) 2005-2008, Zend Technologies USA, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+
+    * Neither the name of Zend Technologies USA, Inc. nor the names of its
+      contributors may be used to endorse or promote products derived from this
+      software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 96 - 0
README.txt

@@ -0,0 +1,96 @@
+Welcome to the Zend Framework 1.8 Release! 
+
+RELEASE INFORMATION
+---------------
+Zend Framework 1.8 Release (revision 15226).
+Released on April 30, 2009.
+
+NEW FEATURES
+------------
+
+* Zend_Tool, contributed by Ralph Schindler
+* Zend_Application, contributed by Ben Scholzen and Matthew Weier O'Phinney
+* Zend_Loader_Autoloader and Zend_Loader_Autoloader_Resource,
+    contributed by Matthew Weier O'Phinney
+* Zend_Navigation, contributed by Robin Skoglund
+* Zend_CodeGenerator, by Ralph Schindler
+* Zend_Reflection, Ralph Schindler and Matthew Weier O'Phinney
+* Zend Server backend for Zend_Cache, contributed by Alexander Veremyev
+* Zend_Service_Amazon_Ec2, contributed by Jon Whitcraft
+* Zend_Service_Amazon_S3, Justin Plock and Stas Malyshev
+* Zend_Filter_Encrypt, contributed by Thomas Weidner
+* Zend_Filter_Decrypt, contributed by Thomas Weidner
+* Zend_Filter_LocalizedToNormalized and _NormalizedToLocalized,
+    contributed by Thomas Weidner
+* Support for file upload progress support in Zend_File_Transfer,
+    contributed by Thomas Weidner
+* Translation-aware routes, contributed by Ben Scholzen
+* Zend_Json expression support, contributed by Benjamin Eberlei and
+    Oscar Reales
+* Zend_Http_Client_Adapter_Curl, contributed by Benjamin Eberlei
+* SOAP input and output header support, contributed by Alexander Veremyev
+* Support for keyword field search using query strings,
+    contributed by Alexander Veremyev
+* Support for searching across multiple indexes in Zend_Search_Lucene,
+    contributed by Alexander Veremyev
+* Significant improvements for Zend_Search_Lucene search result match
+    highlighting capabilities, contributed by Alexander Veremyev
+* Support for page scaling, shifting and skewing in Zend_Pdf,
+    contributed by Alexander Veremyev
+* Zend_Tag_Cloud, contributed by Ben Scholzen
+* Locale support in Zend_Validate_Int and Zend_Validate_Float,
+    contributed by Thomas Weidner
+* Phonecode support in Zend_Locale, contributed by Thomas Weidner
+* Zend_Validate_Db_RecordExists and _RecordNotExists, contributed by
+    Ryan Mauger
+* Zend_Validate_Iban, contributed by Thomas Weidner
+* Zend_Validate_File_WordCount, contributed by Thomas Weidner
+
+A detailed list of all features and bug fixes in this release may be found at:
+
+http://framework.zend.com/issues/secure/IssueNavigator.jspa?requestId=11002
+
+SYSTEM REQUIREMENTS
+-------------------
+
+Zend Framework requires PHP 5.2.4 or later. Please see our reference
+guide for more detailed system requirements:
+
+http://framework.zend.com/manual/en/requirements.html
+
+INSTALLATION
+------------
+
+Please see INSTALL.txt.
+
+QUESTIONS AND FEEDBACK
+----------------------
+
+Online documentation can be found at http://framework.zend.com/manual.
+Questions that are not addressed in the manual should be directed to the
+appropriate mailing list:
+
+http://framework.zend.com/wiki/display/ZFDEV/Mailing+Lists
+
+If you find code in this release behaving in an unexpected manner or
+contrary to its documented behavior, please create an issue in the Zend
+Framework issue tracker at:
+
+http://framework.zend.com/issues
+
+If you would like to be notified of new releases, you can subscribe to
+the fw-announce mailing list by sending a blank message to
+fw-announce-subscribe@lists.zend.com.
+
+LICENSE
+-------
+
+The files in this archive are released under the Zend Framework license.
+You can find a copy of this license in LICENSE.txt.
+
+ACKNOWLEDGEMENTS
+----------------
+
+The Zend Framework team would like to thank all the contributors to the Zend
+Framework project, our corporate sponsor, and you, the Zend Framework user.
+Please visit us sometime soon at http://framework.zend.com.

+ 44 - 0
bin/zf.bat

@@ -0,0 +1,44 @@
+@ECHO off
+REM Zend Framework
+REM
+REM LICENSE
+REM
+REM This source file is subject to the new BSD license that is bundled
+REM with this package in the file LICENSE.txt.
+REM It is also available through the world-wide-web at this URL:
+REM http://framework.zend.com/license/new-bsd
+REM If you did not receive a copy of the license and are unable to
+REM obtain it through the world-wide-web, please send an email
+REM to license@zend.com so we can send you a copy immediately.
+REM
+REM Zend
+REM Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+REM http://framework.zend.com/license/new-bsd     New BSD License
+
+
+REM Test to see if this was installed via pear
+SET ZTMPZTMPZTMPZ=@ph
+SET TMPZTMPZTMP=%ZTMPZTMPZTMPZ%p_bin@
+REM below @php_bin@
+FOR %%x IN ("@php_bin@") DO (if %%x=="%TMPZTMPZTMP%" GOTO :NON_PEAR_INSTALLED)
+
+GOTO PEAR_INSTALLED
+
+:NON_PEAR_INSTALLED
+REM Assume php.exe is executable, and that zf.php will reside in the
+REM same file as this one
+SET PHP_BIN=php.exe
+SET PHP_DIR=%~dp0
+GOTO RUN
+
+:PEAR_INSTALLED
+REM Assume this was installed via PEAR and use replacements php_bin & php_dir
+SET PHP_BIN=@php_bin@
+SET PHP_DIR=@php_dir@
+GOTO RUN
+
+:RUN
+SET ZF_SCRIPT=%PHP_DIR%\zf.php
+"%PHP_BIN%" -d safe_mode=Off -f "%ZF_SCRIPT%" -- %*
+
+

+ 78 - 0
bin/zf.php

@@ -0,0 +1,78 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Tool
+ * @subpackage Framework
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id$
+ */
+
+$_zf['original'] = get_include_path();
+
+// if ZF is not in the include_path, but relative to this file, put it in the include_path
+if (($_zf['prepend'] = getenv('ZEND_TOOL_INCLUDE_PATH_PREPEND')) || ($_zf['whole'] = getenv('ZEND_TOOL_INCLUDE_PATH'))) {
+    if (isset($_zf['prepend']) && ($_zf['prepend'] !== false) && (($_zf['prependRealpath'] = realpath($_zf['prepend'])) !== false)) {
+        set_include_path($_zf['prependRealpath'] . PATH_SEPARATOR . $_zf['original']);
+    } elseif (isset($_zf['whole']) && ($_zf['whole'] !== false) && (($_zf['wholeRealpath'] = realpath($_zf['whole'])) !== false)) {
+        set_include_path($_zf['wholeRealpath']);
+    }
+} 
+
+// assume the include_path is good, and load the client/console
+if ((@include_once 'Zend/Tool/Framework/Client/Console.php') === false) {
+    // last chance, perhaps we can find zf relative to THIS file, if so, lets run
+    $_zf['relativePath'] = dirname(__FILE__) . '/../library/';
+    if (file_exists($_zf['relativePath'] . 'Zend/Tool/Framework/Client/Console.php')) {
+        set_include_path(realpath($_zf['relativePath']) . PATH_SEPARATOR . get_include_path());
+        include_once 'Zend/Tool/Framework/Client/Console.php';
+    }
+}
+
+if (!class_exists('Zend_Tool_Framework_Client_Console')) {
+    echo <<<EOS
+    
+***************************** ZF ERROR ********************************
+In order to run the zf command, you need to ensure that Zend Framework
+is inside your include_path.  If you are running this tool without 
+ZendFramework in your include_path, you can alternatively set one of 
+two environment variables to for this tool to work:
+
+a) ZEND_TOOL_INCLUDE_PATH_PREPEND="/path/to/ZendFramework/library"
+
+OR alternatively
+
+b) ZEND_TOOL_INCLUDE_PATH="/path/to/ZendFramework/library"
+
+The former (a) will make the specified Zend Framework first in the
+include_path whereas the latter (b) will replace the include_path
+with the specified path.
+
+Information:
+
+EOS;
+
+    echo '    original include_path: ' . $_zf['original'] . PHP_EOL;
+    echo '    attempted include_path: ' . get_include_path() . PHP_EOL;
+    echo '    script location: ' . $_SERVER['SCRIPT_NAME'] . PHP_EOL;
+    exit(1);    
+}
+
+// cleanup the global space
+unset($_zf);
+
+// run tool
+Zend_Tool_Framework_Client_Console::main();
+exit(0);

+ 44 - 0
bin/zf.sh

@@ -0,0 +1,44 @@
+#!/bin/sh
+
+#############################################################################
+# Zend Framework
+#
+# LICENSE
+#
+# This source file is subject to the new BSD license that is bundled
+# with this package in the file LICENSE.txt.
+# It is also available through the world-wide-web at this URL:
+# http://framework.zend.com/license/new-bsd
+# If you did not receive a copy of the license and are unable to
+# obtain it through the world-wide-web, please send an email
+# to license@zend.com so we can send you a copy immediately.
+#
+# Zend
+# Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+# http://framework.zend.com/license/new-bsd     New BSD License
+#############################################################################
+
+
+# find php: pear first, command -v second, straight up php lastly
+if test "@php_bin@" != '@'php_bin'@'; then
+    PHP_BIN="@php_bin@"
+elif command -v php 1>/dev/null 2>/dev/null; then
+    PHP_BIN=`command -v php`
+else
+    PHP_BIN=php
+fi
+
+# find zf.php: pear first, same directory 2nd, 
+if test "@php_dir@" != '@'php_dir'@'; then
+    PHP_DIR="@php_dir@"
+else
+    SELF_LINK="$0"
+    SELF_LINK_TMP="$(readlink "$SELF_LINK")"
+    while test -n "$SELF_LINK_TMP"; do
+        SELF_LINK="$SELF_LINK_TMP"
+        SELF_LINK_TMP="$(readlink "$SELF_LINK")"
+    done
+    PHP_DIR="$(dirname "$SELF_LINK")"
+fi
+
+$PHP_BIN -d safe_mode=Off -f $PHP_DIR/zf.php -- $@

+ 39 - 0
demos/Zend/Feeds/consume-feed.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Feed
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Consume an RSS feed and display all of the titles and 
+ * associated links within.
+ */
+
+/**
+ * @see Zend_Feed
+ */
+require_once 'Zend/Feed.php';
+
+$feed = Zend_Feed::import('http://news.google.com/?output=rss');
+
+foreach ($feed->items as $item) {
+    
+    echo "<p>" . $item->title() . "<br />";
+    echo $item->link()  . "</p>";
+    
+}

+ 373 - 0
demos/Zend/Gdata/Blogger.php

@@ -0,0 +1,373 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/*
+* This sample utilizes the Zend Gdata Client Library, which can be 
+* downloaded from: http://framework.zend.com/download
+*
+* This sample is meant to show basic CRUD (Create, Retrieve, Update
+* and Delete) functionality of the Blogger data API, and can only
+* be run from the command line.
+*
+* To run the sample:
+* php Blogger.php --user=email@email.com --pass=password
+*/
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_Query
+ */
+Zend_Loader::loadClass('Zend_Gdata_Query');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+
+/**
+ * Class that contains all simple CRUD operations for Blogger. 
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class SimpleCRUD
+{
+    /**
+     * $blogID - Blog ID used for demo operations
+     *
+     * @var string
+     */
+    public $blogID; 
+
+    /**
+     * $gdClient - Client class used to communicate with the Blogger service
+     *
+     * @var Zend_Gdata_Client
+     */
+    public $gdClient; 
+    
+
+    /**
+     * Constructor for the class. Takes in user credentials and generates the  
+     * the authenticated client object.
+     *
+     * @param  string $email    The user's email address.
+     * @param  string $password The user's password.
+     * @return void
+     */
+    public function __construct($email, $password)
+    {
+        $client = Zend_Gdata_ClientLogin::getHttpClient($email, $password, 'blogger');
+        $this->gdClient = new Zend_Gdata($client);
+    }
+    
+    /**
+     * This function retrieves all the blogs associated with the authenticated  
+     * user and prompts the user to choose which to manipulate.
+     *
+     * Once the index is selected by the user, the corresponding blogID is 
+     * extracted and stored for easy access. 
+     *
+     * @return void
+     */
+    public function promptForBlogID()
+    {
+        $query = new Zend_Gdata_Query('http://www.blogger.com/feeds/default/blogs');
+        $feed = $this->gdClient->getFeed($query);
+        $this->printFeed($feed);
+        $input = getInput("\nSelection");
+
+        //id text is of the form: tag:blogger.com,1999:user-blogID.blogs
+        $idText = split('-', $feed->entries[$input]->id->text);
+        $this->blogID = $idText[2];
+    }
+    
+    /**
+     * This function creates a new Zend_Gdata_Entry representing a blog
+     * post, and inserts it into the user's blog. It also checks for
+     * whether the post should be added as a draft or as a published
+     * post.
+     *
+     * @param  string  $title   The title of the blog post.
+     * @param  string  $content The body of the post.
+     * @param  boolean $isDraft Whether the post should be added as a draft or as a published post
+     * @return string The newly created post's ID
+     */
+    public function createPost($title, $content, $isDraft=False)
+    {
+        // We're using the magic factory method to create a Zend_Gdata_Entry.
+        // http://framework.zend.com/manual/en/zend.gdata.html#zend.gdata.introdduction.magicfactory
+        $entry = $this->gdClient->newEntry();
+    
+        $entry->title = $this->gdClient->newTitle(trim($title));
+        $entry->content = $this->gdClient->newContent(trim($content));
+        $entry->content->setType('text'); 
+        $uri = "http://www.blogger.com/feeds/" . $this->blogID . "/posts/default";  
+        
+        if ($isDraft)
+        {
+            $control = $this->gdClient->newControl(); 
+            $draft = $this->gdClient->newDraft('yes');
+            $control->setDraft($draft);  
+            $entry->control = $control; 
+        }        
+
+        $createdPost = $this->gdClient->insertEntry($entry, $uri);
+        //format of id text: tag:blogger.com,1999:blog-blogID.post-postID 
+        $idText = split('-', $createdPost->id->text);
+        $postID = $idText[2];
+
+        return $postID; 
+    }
+    
+    /**
+     * Prints the titles of all the posts in the user's blog.
+     *
+     * @return void
+     */
+    public function printAllPosts()
+    {
+        $query = new Zend_Gdata_Query('http://www.blogger.com/feeds/' . $this->blogID . '/posts/default');
+        $feed = $this->gdClient->getFeed($query);
+        $this->printFeed($feed);
+    }
+
+    /**
+     * Retrieves the specified post and updates the title and body. Also sets
+     * the post's draft status.
+     *
+     * @param string  $postID         The ID of the post to update. PostID in <id> field:
+     *                                tag:blogger.com,1999:blog-blogID.post-postID
+     * @param string  $updatedTitle   The new title of the post.
+     * @param string  $updatedContent The new body of the post.
+     * @param boolean $isDraft        Whether the post will be published or saved as a draft.
+     * @return Zend_Gdata_Entry The updated post. 
+     */
+    public function updatePost($postID, $updatedTitle, $updatedContent, $isDraft)
+    {
+        $query = new Zend_Gdata_Query('http://www.blogger.com/feeds/' . $this->blogID . '/posts/default/' . $postID); 
+        $postToUpdate = $this->gdClient->getEntry($query);
+        $postToUpdate->title->text = $this->gdClient->newTitle(trim($updatedTitle));
+        $postToUpdate->content->text = $this->gdClient->newContent(trim($updatedContent));
+
+        if ($isDraft) {
+            $draft = $this->gdClient->newDraft('yes'); 
+        } else {
+            $draft = $this->gdClient->newDraft('no');
+        }
+   
+        $control = $this->gdClient->newControl();
+        $control->setDraft($draft);
+        $postToUpdate->control = $control;
+        $updatedPost = $postToUpdate->save();
+        
+        return $updatedPost; 
+    }
+
+    /**
+     * This function uses query parameters to retrieve and print all posts 
+     * within a specified date range.
+     *
+     * @param  string $startDate Beginning date, inclusive. Preferred format is a RFC-3339 date,
+     *                           though other formats are accepted. 
+     * @param  string $endDate   End date, exclusive.
+     * @return void  
+     */
+    public function printPostsInDateRange($startDate, $endDate)
+    {
+        $query = new Zend_Gdata_Query('http://www.blogger.com/feeds/' . $this->blogID . '/posts/default'); 
+        $query->setParam('published-min', $startDate);
+        $query->setParam('published-max', $endDate); 
+   
+        $feed = $this->gdClient->getFeed($query); 
+        $this->printFeed($feed); 
+    }
+
+    /** 
+     * This function creates a new comment and adds it to the specified post.
+     * A comment is created as a Zend_Gdata_Entry.
+     *
+     * @param  string $postID      The ID of the post to add the comment to. PostID
+     *                             in the <id> field: tag:blogger.com,1999:blog-blogID.post-postID
+     * @param  string $commentText The text of the comment to add. 
+     * @return string The ID of the newly created comment.
+     */
+    public function createComment($postID, $commentText)
+    {
+        $uri = 'http://www.blogger.com/feeds/' . $this->blogID . '/' . $postID . '/comments/default'; 
+        
+        $newComment = $this->gdClient->newEntry(); 
+        $newComment->content = $this->gdClient->newContent($commentText); 
+        $newComment->content->setType('text'); 
+        $createdComment = $this->gdClient->insertEntry($newComment, $uri); 
+        
+        echo 'Added new comment: ' . $createdComment->content->text . "\n";
+        // Edit link follows format: /feeds/blogID/postID/comments/default/commentID 
+        $editLink = split('/', $createdComment->getEditLink()->href);
+        $commentID = $editLink[8];      
+ 
+        return $commentID; 
+    }
+
+    /** 
+     * This function prints all comments associated with the specified post.
+     *
+     * @param  string $postID The ID of the post whose comments we'll print.
+     * @return void
+     */
+    public function printAllComments($postID)
+    {
+        $query = new Zend_Gdata_Query('http://www.blogger.com/feeds/' . $this->blogID . '/' . $postID . '/comments/default'); 
+        $feed = $this->gdClient->getFeed($query);
+        $this->printFeed($feed); 
+    }
+
+    /** 
+     * This function deletes the specified comment from a post.
+     *
+     * @param  string $postID    The ID of the post where the comment is. PostID in
+     *                           the <id> field: tag:blogger.com,1999:blog-blogID.post-postID
+     * @param  string $commentID The ID of the comment to delete. The commentID
+     *                           in the editURL: /feeds/blogID/postID/comments/default/commentID
+     * @return void 
+     */
+    public function deleteComment($postID, $commentID)
+    {
+        $uri = 'http://www.blogger.com/feeds/' . $this->blogID . '/' . $postID . '/comments/default/' . $commentID; 
+        $this->gdClient->delete($uri); 
+    }
+
+    /** 
+     * This function deletes the specified post.
+     *
+     * @param  string $postID The ID of the post to delete.
+     * @return void
+     */
+    public function deletePost($postID)
+    {
+        $uri = 'http://www.blogger.com/feeds/' . $this->blogID . '/posts/default/' . $postID; 
+        $this->gdClient->delete($uri);  
+    }
+
+    /** 
+     * Helper function to print out the titles of all supplied Blogger
+     * feeds.
+     *
+     * @param  Zend_Gdata_Feed The feed to print.
+     * @return void
+     */
+    public function printFeed($feed)
+    {
+        $i = 0;
+        foreach($feed->entries as $entry) 
+        {
+            echo "\t" . $i ." ". $entry->title->text . "\n";
+            $i++;
+        }
+    }
+   
+    /**
+     * Runs the sample.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        echo "Note: This sample may Create, Read, Update and Delete data " .
+             "stored in the account provided.  Please exit now if you provided " .
+             "an account which contains important data.\n\n";
+        $this->promptForBlogID();
+        
+        echo "Creating a post.\n";
+        $this->createPost('Hello, world!', 'I am on the intarweb!', False);
+        
+        echo "Creating a draft post.\n"; 
+        $postID = $this->createPost('Salutations, world!', 'Does not sound right.. must work on title.', True); 
+        
+        echo "Updating the previous post and publishing it.\n";
+        $updatedPost = $this->updatePost($postID, 'Hello, world, it is.', 'There we go.', False);
+        echo "The new title of the post is: " . $updatedPost->title->text . "\n";
+        echo "The new body of the post is: " . $updatedPost->content->text . "\n";        
+
+        echo "Adding a comment to the previous post.\n"; 
+        $this->createComment($postID, 'I am so glad this is public now.');
+
+        echo "Adding another comment.\n";  
+        $commentID = $this->createComment($postID, 'This is a spammy comment.'); 
+
+        echo "Deleting the previous comment.\n"; 
+        $this->deleteComment($postID, $commentID);
+
+        echo "Printing all posts.\n";
+        $this->printAllPosts();
+
+        echo "Printing posts between 2007-01-01 and 2007-03-01.\n"; 
+        $this->printPostsInDateRange('2007-01-01','2007-06-30');
+ 
+        echo "Deleting the post titled: " . $updatedPost->title->text . "\n";
+        $this->deletePost($postID); 
+    }
+}
+
+/**
+ * Gets credentials from user.
+ *
+ * @param  string $text
+ * @return string Index of the blog the user has chosen. 
+ */
+function getInput($text)
+{
+    echo $text.': ';
+    return trim(fgets(STDIN));
+}
+
+$user = null;
+$pass = null;
+
+// process command line options
+foreach ($argv as $argument) {
+    $argParts = split('=', $argument);
+    if ($argParts[0] == '--user') {
+        $user = $argParts[1];
+    } else if ($argParts[0] == '--pass') {
+        $pass = $argParts[1];
+    }
+}
+
+if (($user == null) || ($pass == null)) {
+    exit("php Blogger.php --user=[username] --pass=[password]\n");
+}
+
+$sample = new SimpleCRUD($user, $pass); 
+$sample->run();

+ 136 - 0
demos/Zend/Gdata/BooksBrowser/books_browser.css

@@ -0,0 +1,136 @@
+body {
+  background-color: white;
+  color: black;
+  font-family: Arial, sans-serif;
+  font-size: small;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+.thumbnail img {
+  border-color:black;
+  border-width:1;
+  border-style:solid;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 0;
+  vertical-align: top;
+  text-align: left;
+  font-size: small;
+}
+
+a:link {
+  color: #0000cc;
+}
+
+a:active {
+  color: #cc0000;
+}
+
+a:visited {
+  color: #551a8b;
+}
+
+h1 {
+  font-size: x-large;
+  margin-top: 0px;
+  margin-bottom: 5px;
+}
+
+h2 {
+  font-size: large;
+}
+
+form {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+.volumeList td {
+  padding-bottom: 5px;
+  padding-right: 5px;
+}
+
+#titleBar {
+  border: 1px solid silver;
+  background-color: #e5ecf9;
+  font-size: large;
+  font-weight: bold;
+  margin: 0;
+  padding: 0;
+  padding-top: 5px;
+  padding-bottom: 10px;
+  padding-left: 10px;
+  padding-right: 10px;
+  margin-top: 5px;
+  margin-bottom: 15px;
+}
+
+#titleText {
+  float: left;
+}
+
+#mainSearchBox {
+  background-color: #e5ecf9;
+  border: 1px solid silver;
+  width: 300;
+  padding-top: 5px;
+  padding-bottom: 10px;
+  padding-left: 10px;
+  padding-right: 10px;
+}
+
+#searchResults {
+  width: 100%;
+}
+
+.volumeList td
+{ 
+  border-top: 1px solid #aaaaaa;
+  padding: 6px;
+}
+
+.thumbnail 
+{ 
+  height: 80px;
+  padding: 3px;
+}
+
+.previewbutton
+{ 
+  border: 0px;
+  margin: 6px 0px 6px 0px;
+}
+
+#resultcell
+{ 
+  padding-right: 20px;
+}
+
+#previewcell
+{ 
+  border-left: 1px dotted #aaa;
+  padding-left: 20px;
+  display: none;
+  padding-right: 20px;
+}
+
+#viewport {
+  height: 500px;
+  width: 100%;
+  border: 1px solid #aaa;
+  
+}
+
+/* Google Preview: Boilerplate styling */
+#viewport { font-size: 16px; line-height: 1; }
+#viewport img, #viewport table, #viewport div, #viewport td
+{ border: 0;  padding: 0; margin: 0; background: none }
+#viewport td { vertical-align: middle }
+

+ 155 - 0
demos/Zend/Gdata/BooksBrowser/index.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata_Books
+ */
+Zend_Loader::loadClass('Zend_Gdata_Books');
+
+/**
+ * Return a comma separated string representing the elements of an array
+ *
+ * @param Array $elements The array of elements
+ * @return string Comma separated string
+ */
+function printArray($elements) {
+    $result = '';
+    foreach ($elements as $element) {
+      if (!empty($result)) $result = $result.', ';
+      $result = $result.$element;
+    }
+    return $result;
+}
+
+
+/**
+ * Echo the list of videos in the specified feed.
+ *
+ * @param Zend_Gdata_Books_BookFeed $feed The video feed
+ * @return void
+ */
+function echoBookList($feed)
+{
+    print <<<HTML
+    <table><tr><td id="resultcell">
+    <div id="searchResults">
+        <table class="volumeList"><tbody width="100%">
+HTML;
+    $flipflop = false;
+    foreach ($feed as $entry) {
+        $title = printArray($entry->getTitles());
+        $volumeId = $entry->getVolumeId();
+        if ($thumbnailLink = $entry->getThumbnailLink()) {
+            $thumbnail = $thumbnailLink->href;
+        } else {
+            $thumbnail = null;
+        }
+        $preview = $entry->getPreviewLink()->href;
+        $embeddability = $entry->getEmbeddability()->getValue();
+        $creators = printArray($entry->getCreators());
+        if (!empty($creators)) $creators = "by " . $creators;
+        if ($embeddability ==
+            "http://schemas.google.com/books/2008#embeddable") {
+            $preview_link = '<a href="javascript:load_viewport(\''.
+                $preview.'\',\'viewport\');">'.
+                '<img class="previewbutton" src="http://code.google.com/' .
+                'apis/books/images/gbs_preview_button1.png" />' .
+                '</a><br>';
+        } else {
+            $preview_link = '';
+        }
+        $thumbnail_img = (!$thumbnail) ? '' : '<a href="'.$preview.
+            '"><img src="'.$thumbnail.'"/></a>';
+
+        print <<<HTML
+        <tr>
+        <td><div class="thumbnail">
+            $thumbnail_img
+        </div></td>
+        <td width="100%">
+            <a href="${preview}">$title</a><br>
+            $creators<br>
+            $preview_link
+        </td></tr>
+HTML;
+    }
+    print <<<HTML
+    </table></div></td>
+        <td width=50% id="previewcell"><div id="viewport"></div>&nbsp;
+    </td></tr></table><br></body></html>
+HTML;
+}
+
+/*
+ * The main controller logic of the Books volume browser demonstration app.
+ */
+$queryType = isset($_GET['queryType']) ? $_GET['queryType'] : null;
+
+include 'interface.html';
+
+if ($queryType === null) {
+    /* display the entire interface */
+} else {
+    $books = new Zend_Gdata_Books();
+    $query = $books->newVolumeQuery();
+    
+    /* display a list of volumes */
+    if (isset($_GET['searchTerm'])) {
+        $searchTerm = $_GET['searchTerm'];
+        $query->setQuery($searchTerm);
+    }
+    if (isset($_GET['startIndex'])) {
+        $startIndex = $_GET['startIndex'];
+        $query->setStartIndex($startIndex);
+    }
+    if (isset($_GET['maxResults'])) {
+        $maxResults = $_GET['maxResults'];
+        $query->setMaxResults($maxResults);
+    }
+    if (isset($_GET['minViewability'])) {
+        $minViewability = $_GET['minViewability'];
+        $query->setMinViewability($minViewability);
+    }
+    
+    /* check for one of the restricted feeds, or list from 'all' videos */
+    switch ($queryType) {
+    case 'full_view':
+    case 'partial_view':
+        $query->setMinViewability($queryType);
+        echo 'Requesting feed: ' . ($query->getQueryUrl()) . '<br><br>';
+        $feed = $books->getVolumeFeed($query);
+        break;
+    case 'all':
+        echo 'Requesting feed: ' . ($query->getQueryUrl()) . '<br><br>';
+        $feed = $books->getVolumeFeed($query);
+        break;
+    default:
+        echo 'ERROR - unknown queryType - "' . $queryType . '"';
+        break;
+    }
+    echoBookList($feed);
+}
+

+ 81 - 0
demos/Zend/Gdata/BooksBrowser/interface.html

@@ -0,0 +1,81 @@
+<!---
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ *
+ */
+-->
+<html>
+<head>
+  <title>Books Data API Browser in PHP</title>
+  <link href="books_browser.css" type="text/css" rel="stylesheet"/>
+  <script type="text/javascript" src="http://www.google.com/jsapi">
+  </script>
+  <script type="text/javascript">
+    function load_viewport(identifier, viewport_div_id) {
+      var viewport_div = document.getElementById(viewport_div_id);
+      var rightpane_div = viewport_div.parentNode;
+      rightpane_div.style.display = 'table-cell';
+      viewport_div.innerHTML = 'Loading...';
+
+      var viewer = new google.books.DefaultViewer(viewport_div);
+      viewer.load(identifier, handle_not_found);
+    }
+
+    function on_load() {
+    }
+
+    function handle_not_found() {
+      var viewport_div = document.getElementById(viewport_div_id);
+      viewport_div.parentNode.style.display = 'none';
+    } 
+
+    google.load('books', '0');
+    google.setOnLoadCallback(on_load);
+  </script>
+
+</head>
+<body>
+  <script>
+  </script>
+<div id="titleBar">
+  <div id="titleText"><h1>Books Data API Browser in PHP</h1></div>
+  <br />
+</div>
+<br clear="all" />
+<div id="mainSearchBox">
+  <h2>Search Books:</h2>
+  <form id="mainSearchForm" action="index.php">
+    <select name="queryType">
+      <option value="all" selected="true">All Books</option>
+      <option value="partial_view">Limited preview and full view</option>
+      <option value="full_view">Full view books only</option>
+    </select>
+    <input name="maxResults" type="hidden" value="6">
+    <input name="searchTerm" type="text" value="">
+    <input type="submit" value="Search">
+    <a href="http://www.google.com"><img 
+      src="http://books.google.com/googlebooks/images/poweredby.png"
+      border="0" width="62" height="30" align="absbottom"
+      style="position:relative; top: 6px; padding-left: 10px"></a>
+  </form>
+</div>
+<br>
+<br clear="all" />
+
+

+ 841 - 0
demos/Zend/Gdata/Calendar.php

@@ -0,0 +1,841 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the Google Calendar data API.  Utilizes the 
+ * Zend Framework Gdata components to communicate with the Google API.
+ * 
+ * Requires the Zend Framework Gdata components and PHP >= 5.1.4
+ *
+ * You can run this sample both from the command line (CLI) and also
+ * from a web browser.  When running through a web browser, only
+ * AuthSub and outputting a list of calendars is demonstrated.  When
+ * running via CLI, all functionality except AuthSub is available and dependent
+ * upon the command line options passed.  Run this script without any
+ * command line options to see usage, eg:
+ *     /usr/local/bin/php -f Calendar.php
+ *
+ * More information on the Command Line Interface is available at:
+ *     http://www.php.net/features.commandline
+ *
+ * NOTE: You must ensure that the Zend Framework is in your PHP include
+ * path.  You can do this via php.ini settings, or by modifying the 
+ * argument to set_include_path in the code below.
+ *
+ * NOTE: As this is sample code, not all of the functions do full error
+ * handling.  Please see getEvent for an example of how errors could
+ * be handled and the online code samples for additional information.
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_AuthSub
+ */
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+/**
+ * @see Zend_Gdata_HttpClient
+ */
+Zend_Loader::loadClass('Zend_Gdata_HttpClient');
+
+/**
+ * @see Zend_Gdata_Calendar
+ */
+Zend_Loader::loadClass('Zend_Gdata_Calendar');
+
+/**
+ * @var string Location of AuthSub key file.  include_path is used to find this
+ */
+$_authSubKeyFile = null; // Example value for secure use: 'mykey.pem'
+
+/**
+ * @var string Passphrase for AuthSub key file.
+ */
+$_authSubKeyFilePassphrase = null;
+
+/**
+ * Returns the full URL of the current page, based upon env variables
+ * 
+ * Env variables used:
+ * $_SERVER['HTTPS'] = (on|off|)
+ * $_SERVER['HTTP_HOST'] = value of the Host: header
+ * $_SERVER['SERVER_PORT'] = port number (only used if not http/80,https/443)
+ * $_SERVER['REQUEST_URI'] = the URI after the method of the HTTP request
+ *
+ * @return string Current URL
+ */
+function getCurrentUrl() 
+{
+  global $_SERVER;
+
+  /**
+   * Filter php_self to avoid a security vulnerability.
+   */
+  $php_request_uri = htmlentities(substr($_SERVER['REQUEST_URI'], 0, strcspn($_SERVER['REQUEST_URI'], "\n\r")), ENT_QUOTES);
+
+  if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
+    $protocol = 'https://';
+  } else {
+    $protocol = 'http://';
+  }
+  $host = $_SERVER['HTTP_HOST'];
+  if ($_SERVER['SERVER_PORT'] != '' &&
+     (($protocol == 'http://' && $_SERVER['SERVER_PORT'] != '80') ||
+     ($protocol == 'https://' && $_SERVER['SERVER_PORT'] != '443'))) {
+    $port = ':' . $_SERVER['SERVER_PORT'];
+  } else {
+    $port = '';
+  }
+  return $protocol . $host . $port . $php_request_uri;
+}
+
+/**
+ * Returns the AuthSub URL which the user must visit to authenticate requests 
+ * from this application.
+ *
+ * Uses getCurrentUrl() to get the next URL which the user will be redirected
+ * to after successfully authenticating with the Google service.
+ *
+ * @return string AuthSub URL
+ */
+function getAuthSubUrl() 
+{
+  global $_authSubKeyFile;
+  $next = getCurrentUrl();
+  $scope = 'http://www.google.com/calendar/feeds/';
+  $session = true;
+  if ($_authSubKeyFile != null) {
+    $secure = true;
+  } else {
+    $secure = false;
+  }
+  return Zend_Gdata_AuthSub::getAuthSubTokenUri($next, $scope, $secure, 
+      $session);
+}
+
+/**
+ * Outputs a request to the user to login to their Google account, including
+ * a link to the AuthSub URL.
+ * 
+ * Uses getAuthSubUrl() to get the URL which the user must visit to authenticate
+ *
+ * @return void
+ */
+function requestUserLogin($linkText) 
+{
+  $authSubUrl = getAuthSubUrl();
+  echo "<a href=\"{$authSubUrl}\">{$linkText}</a>"; 
+}
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using AuthSub authentication.
+ *
+ * Uses the $_SESSION['sessionToken'] to store the AuthSub session token after
+ * it is obtained.  The single use token supplied in the URL when redirected 
+ * after the user succesfully authenticated to Google is retrieved from the 
+ * $_GET['token'] variable.
+ *
+ * @return Zend_Http_Client
+ */
+function getAuthSubHttpClient() 
+{
+  global $_SESSION, $_GET, $_authSubKeyFile, $_authSubKeyFilePassphrase;
+  $client = new Zend_Gdata_HttpClient();
+  if ($_authSubKeyFile != null) {
+    // set the AuthSub key
+    $client->setAuthSubPrivateKeyFile($_authSubKeyFile, $_authSubKeyFilePassphrase, true);
+  }
+  if (!isset($_SESSION['sessionToken']) && isset($_GET['token'])) {
+    $_SESSION['sessionToken'] = 
+        Zend_Gdata_AuthSub::getAuthSubSessionToken($_GET['token'], $client);
+  } 
+  $client->setAuthSubToken($_SESSION['sessionToken']);
+  return $client;
+}
+
+/**
+ * Processes loading of this sample code through a web browser.  Uses AuthSub
+ * authentication and outputs a list of a user's calendars if succesfully 
+ * authenticated.
+ *
+ * @return void
+ */
+function processPageLoad() 
+{
+  global $_SESSION, $_GET;
+  if (!isset($_SESSION['sessionToken']) && !isset($_GET['token'])) {
+    requestUserLogin('Please login to your Google Account.');
+  } else {
+    $client = getAuthSubHttpClient();
+    outputCalendarList($client);
+  }
+}
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using the ClientLogin credentials supplied.
+ *
+ * @param  string $user The username, in e-mail address format, to authenticate
+ * @param  string $pass The password for the user specified
+ * @return Zend_Http_Client
+ */
+function getClientLoginHttpClient($user, $pass) 
+{
+  $service = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
+
+  $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
+  return $client;
+}
+
+/**
+ * Outputs an HTML unordered list (ul), with each list item representing an event
+ * in the user's calendar.  The calendar is retrieved using the magic cookie
+ * which allows read-only access to private calendar data using a special token
+ * available from within the Calendar UI.
+ *
+ * @param  string $user        The username or address of the calendar to be retrieved.
+ * @param  string $magicCookie The magic cookie token
+ * @return void
+ */
+function outputCalendarMagicCookie($user, $magicCookie) 
+{
+  $gdataCal = new Zend_Gdata_Calendar();
+  $query = $gdataCal->newEventQuery();
+  $query->setUser($user);
+  $query->setVisibility('private-' . $magicCookie);
+  $query->setProjection('full');
+  $eventFeed = $gdataCal->getCalendarEventFeed($query);
+  echo "<ul>\n";
+  foreach ($eventFeed as $event) {
+    echo "\t<li>" . $event->title->text . "</li>\n";
+    $sl = $event->getLink('self')->href;
+  }
+  echo "</ul>\n";
+}
+
+/** 
+ * Outputs an HTML unordered list (ul), with each list item representing a
+ * calendar in the authenticated user's calendar list.  
+ *
+ * @param  Zend_Http_Client $client The authenticated client object
+ * @return void
+ */
+function outputCalendarList($client) 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $calFeed = $gdataCal->getCalendarListFeed();
+  echo "<h1>" . $calFeed->title->text . "</h1>\n";
+  echo "<ul>\n";
+  foreach ($calFeed as $calendar) {
+    echo "\t<li>" . $calendar->title->text . "</li>\n";
+  }
+  echo "</ul>\n";
+} 
+
+/**
+ * Outputs an HTML unordered list (ul), with each list item representing an
+ * event on the authenticated user's calendar.  Includes the start time and
+ * event ID in the output.  Events are ordered by starttime and include only
+ * events occurring in the future.
+ *
+ * @param  Zend_Http_Client $client The authenticated client object
+ * @return void
+ */
+function outputCalendar($client) 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $query = $gdataCal->newEventQuery();
+  $query->setUser('default');
+  $query->setVisibility('private');
+  $query->setProjection('full');
+  $query->setOrderby('starttime');
+  $query->setFutureevents(true);
+  $eventFeed = $gdataCal->getCalendarEventFeed($query);
+  // option 2
+  // $eventFeed = $gdataCal->getCalendarEventFeed($query->getQueryUrl());
+  echo "<ul>\n";
+  foreach ($eventFeed as $event) {
+    echo "\t<li>" . $event->title->text .  " (" . $event->id->text . ")\n";
+    // Zend_Gdata_App_Extensions_Title->__toString() is defined, so the
+    // following will also work on PHP >= 5.2.0
+    //echo "\t<li>" . $event->title .  " (" . $event->id . ")\n";
+    echo "\t\t<ul>\n";
+    foreach ($event->when as $when) {
+      echo "\t\t\t<li>Starts: " . $when->startTime . "</li>\n";
+    }
+    echo "\t\t</ul>\n";
+    echo "\t</li>\n";
+  }
+  echo "</ul>\n";
+}
+
+/**
+ * Outputs an HTML unordered list (ul), with each list item representing an
+ * event on the authenticated user's calendar which occurs during the 
+ * specified date range.
+ * 
+ * To query for all events occurring on 2006-12-24, you would query for
+ * a startDate of '2006-12-24' and an endDate of '2006-12-25' as the upper
+ * bound for date queries is exclusive.  See the 'query parameters reference':
+ * http://code.google.com/apis/gdata/calendar.html#Parameters
+ *
+ * @param  Zend_Http_Client $client    The authenticated client object
+ * @param  string           $startDate The start date in YYYY-MM-DD format
+ * @param  string           $endDate   The end date in YYYY-MM-DD format
+ * @return void
+ */
+function outputCalendarByDateRange($client, $startDate='2007-05-01', 
+                                   $endDate='2007-08-01') 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $query = $gdataCal->newEventQuery();
+  $query->setUser('default');
+  $query->setVisibility('private');
+  $query->setProjection('full');
+  $query->setOrderby('starttime');
+  $query->setStartMin($startDate);
+  $query->setStartMax($endDate);
+  $eventFeed = $gdataCal->getCalendarEventFeed($query);
+  echo "<ul>\n";
+  foreach ($eventFeed as $event) {
+    echo "\t<li>" . $event->title->text .  " (" . $event->id->text . ")\n";
+    echo "\t\t<ul>\n";
+    foreach ($event->when as $when) {
+      echo "\t\t\t<li>Starts: " . $when->startTime . "</li>\n";
+    }
+    echo "\t\t</ul>\n";
+    echo "\t</li>\n";
+  }
+  echo "</ul>\n";
+}
+
+/**
+ * Outputs an HTML unordered list (ul), with each list item representing an
+ * event on the authenticated user's calendar which matches the search string
+ * specified as the $fullTextQuery parameter
+ * 
+ * @param  Zend_Http_Client $client        The authenticated client object
+ * @param  string           $fullTextQuery The string for which you are searching
+ * @return void
+ */
+function outputCalendarByFullTextQuery($client, $fullTextQuery='tennis') 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $query = $gdataCal->newEventQuery();
+  $query->setUser('default');
+  $query->setVisibility('private');
+  $query->setProjection('full');
+  $query->setQuery($fullTextQuery);
+  $eventFeed = $gdataCal->getCalendarEventFeed($query);
+  echo "<ul>\n";
+  foreach ($eventFeed as $event) {
+    echo "\t<li>" . $event->title->text .  " (" . $event->id->text . ")\n";
+    echo "\t\t<ul>\n";
+    foreach ($event->when as $when) {
+      echo "\t\t\t<li>Starts: " . $when->startTime . "</li>\n";
+      echo "\t\t</ul>\n";
+      echo "\t</li>\n";
+    }
+  }
+  echo "</ul>\n";
+}
+
+/**
+ * Creates an event on the authenticated user's default calendar with the
+ * specified event details.
+ *
+ * @param  Zend_Http_Client $client    The authenticated client object
+ * @param  string           $title     The event title
+ * @param  string           $desc      The detailed description of the event
+ * @param  string           $where
+ * @param  string           $startDate The start date of the event in YYYY-MM-DD format
+ * @param  string           $startTime The start time of the event in HH:MM 24hr format
+ * @param  string           $endDate   The end date of the event in YYYY-MM-DD format
+ * @param  string           $endTime   The end time of the event in HH:MM 24hr format
+ * @param  string           $tzOffset  The offset from GMT/UTC in [+-]DD format (eg -08)
+ * @return string The ID URL for the event.
+ */
+function createEvent ($client, $title = 'Tennis with Beth', 
+    $desc='Meet for a quick lesson', $where = 'On the courts', 
+    $startDate = '2008-01-20', $startTime = '10:00', 
+    $endDate = '2008-01-20', $endTime = '11:00', $tzOffset = '-08')
+{
+  $gc = new Zend_Gdata_Calendar($client);
+  $newEntry = $gc->newEventEntry();
+  $newEntry->title = $gc->newTitle(trim($title));
+  $newEntry->where  = array($gc->newWhere($where));
+
+  $newEntry->content = $gc->newContent($desc);
+  $newEntry->content->type = 'text';
+
+  $when = $gc->newWhen();
+  $when->startTime = "{$startDate}T{$startTime}:00.000{$tzOffset}:00";
+  $when->endTime = "{$endDate}T{$endTime}:00.000{$tzOffset}:00";
+  $newEntry->when = array($when);
+
+  $createdEntry = $gc->insertEvent($newEntry);
+  return $createdEntry->id->text;
+}
+
+/**
+ * Creates an event on the authenticated user's default calendar using 
+ * the specified QuickAdd string.
+ *
+ * @param  Zend_Http_Client $client       The authenticated client object
+ * @param  string           $quickAddText The QuickAdd text for the event
+ * @return string The ID URL for the event
+ */
+function createQuickAddEvent ($client, $quickAddText) {
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $event = $gdataCal->newEventEntry();
+  $event->content = $gdataCal->newContent($quickAddText);
+  $event->quickAdd = $gdataCal->newQuickAdd(true);
+
+  $newEvent = $gdataCal->insertEvent($event);
+  return $newEvent->id->text;
+}
+
+/**
+ * Creates a new web content event on the authenticated user's default 
+ * calendar with the specified event details. For simplicity, the event 
+ * is created as an all day event and does not include a description.
+ *
+ * @param  Zend_Http_Client $client    The authenticated client object
+ * @param  string           $title     The event title
+ * @param  string           $startDate The start date of the event in YYYY-MM-DD format
+ * @param  string           $endDate   The end time of the event in HH:MM 24hr format
+ * @param  string           $icon      URL pointing to a 16x16 px icon representing the event.
+ * @param  string           $url       The URL containing the web content for the event.
+ * @param  string           $height    The desired height of the web content pane.
+ * @param  string           $width     The desired width of the web content pane.
+ * @param  string           $type      The MIME type of the web content.
+ * @return string The ID URL for the event.
+ */
+function createWebContentEvent ($client, $title = 'World Cup 2006', 
+    $startDate = '2006-06-09', $endDate = '2006-06-09', 
+    $icon = 'http://www.google.com/calendar/images/google-holiday.gif',
+    $url = 'http://www.google.com/logos/worldcup06.gif',
+    $height  = '120', $width = '276', $type = 'image/gif'
+    )
+{
+  $gc = new Zend_Gdata_Calendar($client);
+  $newEntry = $gc->newEventEntry();
+  $newEntry->title = $gc->newTitle(trim($title));
+
+  $when = $gc->newWhen();
+  $when->startTime = $startDate;
+  $when->endTime = $endDate;
+  $newEntry->when = array($when);
+  
+  $wc = $gc->newWebContent();
+  $wc->url = $url;
+  $wc->height = $height;
+  $wc->width = $width;
+
+  $wcLink = $gc->newLink();
+  $wcLink->rel = "http://schemas.google.com/gCal/2005/webContent";
+  $wcLink->title = $title;
+  $wcLink->type = $type;
+  $wcLink->href = $icon;
+  
+  $wcLink->webContent = $wc;
+  $newEntry->link = array($wcLink);
+
+  $createdEntry = $gc->insertEvent($newEntry);
+  return $createdEntry->id->text;
+}
+
+/**
+ * Creates a recurring event on the authenticated user's default calendar with
+ * the specified event details.  
+ *
+ * @param  Zend_Http_Client $client    The authenticated client object
+ * @param  string           $title     The event title
+ * @param  string           $desc      The detailed description of the event
+ * @param  string           $where
+ * @param  string           $recurData The iCalendar recurring event syntax (RFC2445)
+ * @return void
+ */
+function createRecurringEvent ($client, $title = 'Tennis with Beth', 
+    $desc='Meet for a quick lesson', $where = 'On the courts', 
+    $recurData = null)
+{
+  $gc = new Zend_Gdata_Calendar($client);
+  $newEntry = $gc->newEventEntry();
+  $newEntry->title = $gc->newTitle(trim($title));
+  $newEntry->where = array($gc->newWhere($where));
+
+  $newEntry->content = $gc->newContent($desc);
+  $newEntry->content->type = 'text';
+
+  /**
+   * Due to the length of this recurrence syntax, we did not specify
+   * it as a default parameter value directly
+   */
+  if ($recurData == null) {
+    $recurData =
+        "DTSTART;VALUE=DATE:20070501\r\n" .
+        "DTEND;VALUE=DATE:20070502\r\n" .
+        "RRULE:FREQ=WEEKLY;BYDAY=Tu;UNTIL=20070904\r\n";
+  }
+
+  $newEntry->recurrence = $gc->newRecurrence($recurData);
+
+  $gc->post($newEntry->saveXML());
+}
+
+/**
+ * Returns an entry object representing the event with the specified ID.
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $eventId The event ID string
+ * @return Zend_Gdata_Calendar_EventEntry|null if the event is found, null if it's not
+ */
+function getEvent($client, $eventId) 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $query = $gdataCal->newEventQuery();
+  $query->setUser('default');
+  $query->setVisibility('private');
+  $query->setProjection('full');
+  $query->setEvent($eventId);
+
+  try {
+    $eventEntry = $gdataCal->getCalendarEventEntry($query);
+    return $eventEntry;
+  } catch (Zend_Gdata_App_Exception $e) {
+    var_dump($e);
+    return null;
+  }
+}
+
+/**
+ * Updates the title of the event with the specified ID to be
+ * the title specified.  Also outputs the new and old title
+ * with HTML br elements separating the lines
+ *
+ * @param  Zend_Http_Client $client   The authenticated client object
+ * @param  string           $eventId  The event ID string
+ * @param  string           $newTitle The new title to set on this event 
+ * @return Zend_Gdata_Calendar_EventEntry|null The updated entry
+ */
+function updateEvent ($client, $eventId, $newTitle) 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  if ($eventOld = getEvent($client, $eventId)) {
+    echo "Old title: " . $eventOld->title->text . "<br />\n";
+    $eventOld->title = $gdataCal->newTitle($newTitle);
+    try {
+        $eventOld->save();
+    } catch (Zend_Gdata_App_Exception $e) {
+        var_dump($e);
+        return null;
+    }
+    $eventNew = getEvent($client, $eventId);
+    echo "New title: " . $eventNew->title->text . "<br />\n";
+    return $eventNew;
+  } else {
+    return null;
+  }
+}
+
+/**
+ * Adds an extended property to the event specified as a parameter.
+ * An extended property is an arbitrary name/value pair that can be added
+ * to an event and retrieved via the API.  It is not accessible from the
+ * calendar web interface.
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $eventId The event ID string
+ * @param  string           $name    The name of the extended property
+ * @param  string           $value   The value of the extended property
+ * @return Zend_Gdata_Calendar_EventEntry|null The updated entry
+ */
+function addExtendedProperty ($client, $eventId, 
+    $name='http://www.example.com/schemas/2005#mycal.id', $value='1234') 
+{
+  $gc = new Zend_Gdata_Calendar($client);
+  if ($event = getEvent($client, $eventId)) {
+    $extProp = $gc->newExtendedProperty($name, $value);
+    $extProps = array_merge($event->extendedProperty, array($extProp));
+    $event->extendedProperty = $extProps;
+    $eventNew = $event->save();
+    return $eventNew;
+  } else {
+    return null;
+  }
+} 
+
+
+/**
+ * Adds a reminder to the event specified as a parameter.
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $eventId The event ID string
+ * @param  integer          $minutes Minutes before event to set reminder
+ * @return Zend_Gdata_Calendar_EventEntry|null The updated entry
+ */
+function setReminder($client, $eventId, $minutes=15)
+{
+  $gc = new Zend_Gdata_Calendar($client);
+  $method = "alert";
+  if ($event = getEvent($client, $eventId)) {
+    $times = $event->when;
+    foreach ($times as $when) {
+        $reminder = $gc->newReminder();
+        $reminder->setMinutes($minutes);
+        $reminder->setMethod($method);
+        $when->reminder = array($reminder);
+    }
+    $eventNew = $event->save();
+    return $eventNew;
+  } else {
+    return null;
+  }
+}
+
+/**
+ * Deletes the event specified by retrieving the atom entry object
+ * and calling Zend_Feed_EntryAtom::delete() method.  This is for
+ * example purposes only, as it is inefficient to retrieve the entire
+ * atom entry only for the purposes of deleting it.
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $eventId The event ID string
+ * @return void
+ */
+function deleteEventById ($client, $eventId) 
+{
+  $event = getEvent($client, $eventId);
+  $event->delete();
+}
+
+/**
+ * Deletes the event specified by calling the Zend_Gdata::delete()
+ * method.  The URL is typically in the format of:
+ * http://www.google.com/calendar/feeds/default/private/full/<eventId>
+ *
+ * @param  Zend_Http_Client $client The authenticated client object
+ * @param  string           $url    The url for the event to be deleted 
+ * @return void
+ */
+function deleteEventByUrl ($client, $url) 
+{
+  $gdataCal = new Zend_Gdata_Calendar($client);
+  $gdataCal->delete($url);
+}
+
+/**
+ * Main logic for running this sample code via the command line or,
+ * for AuthSub functionality only, via a web browser.  The output of
+ * many of the functions is in HTML format for demonstration purposes,
+ * so you may wish to pipe the output to Tidy when running from the 
+ * command-line for clearer results.
+ *
+ * Run without any arguments to get usage information
+ */
+if (isset($argc) && $argc >= 2) {
+  switch ($argv[1]) {
+    case 'outputCalendar':
+      if ($argc == 4) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        outputCalendar($client);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} " .
+             "<username> <password>\n";
+      }
+      break;
+    case 'outputCalendarMagicCookie':
+      if ($argc == 4) { 
+        outputCalendarMagicCookie($argv[2], $argv[3]); 
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} " .
+             "<username> <magicCookie>\n";
+      }
+      break;
+    case 'outputCalendarByDateRange':
+      if ($argc == 6) {
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        outputCalendarByDateRange($client, $argv[4], $argv[5]);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} " . 
+             "<username> <password> <startDate> <endDate>\n";
+      }
+      break;
+    case 'outputCalendarByFullTextQuery':
+      if ($argc == 5) {
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        outputCalendarByFullTextQuery($client, $argv[4]);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} " . 
+             "<username> <password> <fullTextQuery>\n";
+      }
+      break;
+    case 'outputCalendarList':
+      if ($argc == 4) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        outputCalendarList($client);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} " .
+             "<username> <password>\n";
+      }
+      break;
+    case 'updateEvent':
+      if ($argc == 6) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        updateEvent($client, $argv[4], $argv[5]); 
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<eventId> <newTitle>\n";
+      }
+      break;
+    case 'setReminder':
+      if ($argc == 6) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        setReminder($client, $argv[4], $argv[5]); 
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<eventId> <minutes>\n";
+      }
+      break;
+    case 'addExtendedProperty':
+      if ($argc == 7) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        addExtendedProperty($client, $argv[4], $argv[5], $argv[6]);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<eventId> <name> <value>\n";
+      }
+      break;
+    case 'deleteEventById':
+      if ($argc == 5) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        deleteEventById($client, $argv[4]); 
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<eventId>\n";
+      }
+      break;
+    case 'deleteEventByUrl':
+      if ($argc == 5) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        deleteEventByUrl($client, $argv[4]); 
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<eventUrl>\n";
+      }
+      break;
+    case 'createEvent':
+      if ($argc == 12) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        $id = createEvent($client, $argv[4], $argv[5], $argv[6], $argv[7], 
+            $argv[8], $argv[9], $argv[10], $argv[11]);
+        print "Event created with ID: $id\n";
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<title> <description> <where> " .
+             "<startDate> <startTime> <endDate> <endTime> <tzOffset>\n";
+        echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "'Tennis with Beth' 'Meet for a quick lesson' 'On the courts' " .
+             "'2008-01-01' '10:00' '2008-01-01' '11:00' '-08'\n";
+      }
+      break;
+    case 'createQuickAddEvent':
+      if ($argc == 5) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        $id = createQuickAddEvent($client, $argv[4]);
+        print "Event created with ID: $id\n";
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<quickAddText>\n";
+        echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "'Dinner at the beach on Thursday 8 PM'\n";
+      }
+      break;
+    case 'createWebContentEvent':
+        if ($argc == 12) { 
+          $client = getClientLoginHttpClient($argv[2], $argv[3]);
+          $id = createWebContentEvent($client, $argv[4], $argv[5], $argv[6], 
+              $argv[7], $argv[8], $argv[9], $argv[10], $argv[11]);
+          print "Event created with ID: $id\n";
+        } else {
+          echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+               "<title> <startDate> <endDate> <icon> <url> <height> <width> <type>\n\n";
+          echo "This creates a web content event on 2007/06/09.\n";
+          echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <password> " . 
+               "'World Cup 2006' '2007-06-09' '2007-06-10' " . 
+               "'http://www.google.com/calendar/images/google-holiday.gif' " . 
+               "'http://www.google.com/logos/worldcup06.gif' " . 
+               "'120' '276' 'image/gif'\n";
+        }
+        break;
+    case 'createRecurringEvent':
+      if ($argc == 7) { 
+        $client = getClientLoginHttpClient($argv[2], $argv[3]);
+        createRecurringEvent($client, $argv[4], $argv[5], $argv[6]);
+      } else {
+        echo "Usage: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "<title> <description> <where>\n\n";
+        echo "This creates an all-day event which occurs first on 2007/05/01" .
+             "and repeats weekly on Tuesdays until 2007/09/04\n";
+        echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <password> " . 
+             "'Tennis with Beth' 'Meet for a quick lesson' 'On the courts'\n";
+      }
+      break;
+  } 
+} else if (!isset($_SERVER["HTTP_HOST"]))  {
+  // running from command line, but action left unspecified
+  echo "Usage: php {$argv[0]} <action> [<username>] [<password>] " .
+      "[<arg1> <arg2> ...]\n\n";
+  echo "Possible action values include:\n" .
+       "outputCalendar\n" . 
+       "outputCalendarMagicCookie\n" . 
+       "outputCalendarByDateRange\n" .
+       "outputCalendarByFullTextQuery\n" .
+       "outputCalendarList\n" .
+       "updateEvent\n" .
+       "deleteEventById\n" .
+       "deleteEventByUrl\n" .
+       "createEvent\n" .
+       "createQuickAddEvent\n" .
+       "createWebContentEvent\n" .
+       "createRecurringEvent\n" .
+       "setReminder\n" .
+       "addExtendedProperty\n";
+} else {
+  // running through web server - demonstrate AuthSub
+  processPageLoad();
+}

+ 940 - 0
demos/Zend/Gdata/Docs.php

@@ -0,0 +1,940 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the Google Documents List data API.  Utilizes the 
+ * Zend Framework Gdata components to communicate with the Google API.
+ * 
+ * Requires the Zend Framework Gdata components and PHP >= 5.1.4
+ *
+ * You can run this sample both from the command line (CLI) and also
+ * from a web browser.  When running through a web browser, only
+ * AuthSub and outputting a list of documents is demonstrated.  When
+ * running via CLI, all functionality except AuthSub is available and dependent
+ * upon the command line options passed.  Run this script without any
+ * command line options to see usage, eg:
+ *     /usr/local/bin/php -f Docs.php
+ *
+ * More information on the Command Line Interface is available at:
+ *     http://www.php.net/features.commandline
+ *
+ * NOTE: You must ensure that Zend Framework is in your PHP include
+ * path.  You can do this via php.ini settings, or by modifying the 
+ * argument to set_include_path in the code below.
+ *
+ * NOTE: As this is sample code, not all of the functions do full error
+ * handling.  
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_AuthSub
+ */
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+/**
+ * @see Zend_Gdata_Docs
+ */
+Zend_Loader::loadClass('Zend_Gdata_Docs');
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using the ClientLogin credentials supplied.
+ *
+ * @param  string $user The username, in e-mail address format, to authenticate
+ * @param  string $pass The password for the user specified
+ * @return Zend_Http_Client
+ */
+function getClientLoginHttpClient($user, $pass)
+{
+  $service = Zend_Gdata_Docs::AUTH_SERVICE_NAME;
+  $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
+  return $client;
+}
+
+// ************************ BEGIN CLI SPECIFIC CODE ************************
+
+/**
+ * Display list of valid commands.
+ *
+ * @param  string $executable The name of the current script. This is usually available as $argv[0].
+ * @return void
+ */
+function displayHelp($executable)
+{
+    echo "Usage: php {$executable} <action> [<username>] [<password>] " .
+        "[<arg1> <arg2> ...]\n\n";
+    echo "Possible action values include:\n" .
+        "retrieveAllDocuments\n" .
+        "retrieveWPDocs\n" .
+        "retrieveSpreadsheets\n" .
+        "fullTextSearch\n" .
+        "uploadDocument\n";
+}
+
+/**
+ * Parse command line arguments and execute appropriate function when
+ * running from the command line.
+ *
+ * If no arguments are provided, usage information will be provided.
+ *
+ * @param  array   $argv The array of command line arguments provided by PHP.
+ *                       $argv[0] should be the current executable name or '-' if not available.
+ * @param  integer $argc The size of $argv.
+ * @return void
+ */
+function runCLIVersion($argv, $argc)
+{
+    if (isset($argc) && $argc >= 2) {
+        # Prepare a server connection
+        if ($argc >= 4) {
+            try {
+                $client = getClientLoginHttpClient($argv[2], $argv[3]);
+                $docs = new Zend_Gdata_Docs($client);
+            } catch (Zend_Gdata_App_AuthException $e) {
+                echo "Error: Unable to authenticate. Please check your";
+                echo " credentials.\n";
+                exit(1);
+            }
+        }
+
+        # Dispatch arguments to the desired method
+        switch ($argv[1]) {
+            case 'retrieveAllDocuments':
+                if ($argc >= 4) {
+                    retrieveAllDocuments($docs, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password>\n\n";
+                    echo "This lists all of the documents in the user's";
+                    echo " account.\n";
+                }
+                break;
+            case 'retrieveWPDocs':
+                if ($argc >= 4) {
+                    //echo "!WP Docs:";
+                    //var_dump($docs);
+                    retrieveWPDocs($docs, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password>\n\n";
+                    echo "This lists all of the word processing documents in";
+                    echo " the user's account.\n";
+                }
+                break;
+            case 'retrieveSpreadsheets':
+                if ($argc >= 4) {
+                    retrieveAllDocuments($docs, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password>\n\n";
+                    echo "This lists all of the spreadsheets in the user's";
+                    echo " account.\n";
+                }
+                break;
+            case 'fullTextSearch':
+                if ($argc >= 4) {
+                    // Combine all of the query args into one query string. 
+                    // The command line split the query string on space 
+                    // characters.
+                    $queryString = implode(' ', array_slice($argv, 4));
+                    fullTextSearch($docs, false, $queryString);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password> <query string>\n\n";
+                    echo "This lists all of the documents which contain the";
+                    echo " query string.\n";
+                }
+                break;
+            case 'uploadDocument':
+                if ($argc >= 5) {
+                    // Pass in the file name of the document to be uploaded. 
+                    // Since the document is on this machine, we  do not need
+                    // to set the temporary file name. The temp file name is
+                    // used only when uploading to a webserver.
+                    uploadDocument($docs, false, $argv[4], null);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password> <file_with_path>\n\n";
+                    echo "This lists all of the documents which contain the";
+                    echo " query string.\n";
+                    echo "\nExample: php {$argv[0]} {$argv[1]} <username>";
+                    echo " <password> /tmp/testSpreadsheet.ods\n";
+                }
+                break;
+            default:
+                // Invalid action entered
+                displayHelp($argv[0]);
+        // End switch block
+        }
+    } else {
+        // action left unspecified
+        displayHelp($argv[0]);
+    }
+}
+
+/** 
+ * Displays the titles for the Google Documents entries in the feed. In HTML 
+ * mode, the titles are links which point to the HTML version of the document.
+ *
+ * @param  Zend_Gdata_Docs_DocumentListFeed $feed
+ * @param  boolean                          $html True if output should be formatted for display in
+ *                                          a web browser
+ * @return void
+ */
+function printDocumentsFeed($feed, $html) 
+{
+  if ($html) {echo "<ul>\n";}
+
+  // Iterate over the document entries in the feed and display each document's
+  // title.
+  foreach ($feed->entries as $entry) {
+  
+    if ($html) {
+        // Find the URL of the HTML view of the document.
+        $alternateLink = '';
+        foreach ($entry->link as $link) {
+            if ($link->getRel() === 'alternate') {
+                $alternateLink = $link->getHref();
+            }
+        }
+        // Make the title link to the document on docs.google.com.
+        echo "<li><a href=\"$alternateLink\">\n";
+    }
+
+    echo "$entry->title\n";
+
+    if ($html) {echo "</a></li>\n";}
+  }
+
+  if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Obtain a list of all of a user's docs.google.com documents and print the 
+ * titles to the command line.
+ *
+ * @param  Zend_Gdata_Docs $client The service object to use for communicating with the Google
+ *                                 Documents server.
+ * @param  boolean         $html   True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveAllDocuments($client, $html) 
+{
+  if ($html) {echo "<h2>Your documents</h2>\n";}
+
+  $feed = $client->getDocumentListFeed();
+
+  printDocumentsFeed($feed, $html);
+}
+
+/**
+ * Obtain a list of all of a user's docs.google.com word processing 
+ * documents and print the titles to the command line.
+ *
+ * @param  Zend_Gdata_Docs $client The service object to use for communicating with the Google
+ *                                 Documents server.
+ * @param  boolean         $html   True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveWPDocs($client, $html) 
+{
+  if ($html) {echo "<h2>Your word processing documents</h2>\n";}
+
+  $feed = $client->getDocumentListFeed(
+      'http://docs.google.com/feeds/documents/private/full/-/document');
+
+  printDocumentsFeed($feed, $html);
+}
+
+/**
+ * Obtain a list of all of a user's docs.google.com spreadsheets 
+ * documents and print the titles to the command line.
+ *
+ * @param  Zend_Gdata_Docs $client The service object to use for communicating with the Google
+ *                                 Documents server.
+ * @param  boolean         $html   True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveSpreadsheets($client, $html) 
+{
+  if ($html) {echo "<h2>Your spreadsheets</h2>\n";}
+ 
+  $feed = $client->getDocumentListFeed(
+      'http://docs.google.com/feeds/documents/private/full/-/spreadsheet');
+
+  printDocumentsFeed($feed, $html);
+}
+
+/**
+ * Obtain a list of all of a user's docs.google.com documents 
+ * which match the specified search criteria and print the titles to the 
+ * command line.
+ *
+ * @param  Zend_Gdata_Docs $client The service object to use for communicating with the Google
+ *                                 Documents server.
+ * @param  boolean         $html   True if output should be formatted for display in a web browser.
+ * @param  string          $query  The search query to use
+ * @return void
+ */
+function fullTextSearch($client, $html, $query) 
+{
+  if ($html) {echo "<h2>Documents containing $query</h2>\n";}
+
+  $feed = $client->getDocumentListFeed(
+      'http://docs.google.com/feeds/documents/private/full?q=' . $query);
+
+  printDocumentsFeed($feed, $html);
+}
+
+/**
+ * Upload the specified document
+ *
+ * @param  Zend_Gdata_Docs $docs                  The service object to use for communicating with
+ *                                                the Google Documents server.
+ * @param  boolean         $html                  True if output should be formatted for display in
+ *                                                a web browser.
+ * @param  string          $originalFileName      The name of the file to be uploaded. The mime type
+ *                                                of the file is determined from the extension on
+ *                                                this file name. For example, test.csv is uploaded
+ *                                                as a comma seperated volume and converted into a
+ *                                                spreadsheet.
+ * @param  string          $temporaryFileLocation (optional) The file in which the data for the
+ *                                                document is stored. This is used when the file has
+ *                                                been uploaded from the client's machine to the
+ *                                                server and is stored in a temporary file which
+ *                                                does not have an extension. If this parameter is
+ *                                                null, the file is read from the originalFileName.
+ * @return void
+ */
+function uploadDocument($docs, $html, $originalFileName,
+                        $temporaryFileLocation) {
+  $fileToUpload = $originalFileName;
+  if ($temporaryFileLocation) {
+    $fileToUpload = $temporaryFileLocation;
+  }
+
+  // Upload the file and convert it into a Google Document. The original
+  // file name is used as the title of the document and the mime type
+  // is determined based on the extension on the original file name.
+  $newDocumentEntry = $docs->uploadFile($fileToUpload, $originalFileName,
+      null, Zend_Gdata_Docs::DOCUMENTS_LIST_FEED_URI);
+
+  echo "New Document Title: ";
+
+  if ($html) {
+      // Find the URL of the HTML view of this document.
+      $alternateLink = '';
+      foreach ($newDocumentEntry->link as $link) {
+          if ($link->getRel() === 'alternate') {
+              $alternateLink = $link->getHref();
+          }
+      }
+      // Make the title link to the document on docs.google.com.
+      echo "<a href=\"$alternateLink\">\n";
+  }
+  echo $newDocumentEntry->title."\n";
+  if ($html) {echo "</a>\n";}
+}
+
+// ************************ BEGIN WWW SPECIFIC CODE ************************
+
+/**
+ * Writes the HTML prologue for this app.
+ *
+ * NOTE: We would normally keep the HTML/CSS markup separate from the business
+ *       logic above, but have decided to include it here for simplicity of
+ *       having a single-file sample.
+ *
+ *
+ * @param  boolean $displayMenu (optional) If set to true, a navigation menu is displayed at the top
+ *                              of the page. Default is true.
+ * @return void
+ */
+function startHTML($displayMenu = true)
+{
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+    <title>Documents List API Demo</title>
+
+    <style type="text/css" media="screen">
+        body {
+            font-family: Arial, Helvetica, sans-serif;
+            font-size: small;
+        }
+
+        #header {
+            background-color: #9cF;
+            -moz-border-radius: 5px;
+            -webkit-border-radius: 5px;
+            padding-left: 5px;
+            height: 2.4em;
+        }
+
+        #header h1 {
+            width: 49%;
+            display: inline;
+            float: left;
+            margin: 0;
+            padding: 0;
+            font-size: 2em;
+        }
+
+        #header p {
+            width: 49%;
+            margin: 0;
+            padding-right: 15px;
+            float: right;
+            line-height: 2.4em;
+            text-align: right;
+        }
+
+        .clear {
+            clear:both;
+        }
+
+        h2 {
+            background-color: #ccc;
+            -moz-border-radius: 5px;
+            -webkit-border-radius: 5px;
+            margin-top: 1em;
+            padding-left: 5px;
+        }
+
+        .error {
+            color: red;
+        }
+
+        form {
+            width: 500px;
+            background: #ddf8cc;
+            border: 1px solid #80c605;
+            padding: 0 1em;
+            margin: 1em auto;
+        }
+
+        .warning {
+            width: 500px;
+            background: #F4B5B4;
+            border: 1px solid #900;
+            padding: 0 1em;
+            margin: 1em auto;
+        }
+
+        label {
+            display: block;
+            width: 130px;
+            float: left;
+            text-align: right;
+            padding-top: 0.3em;
+            padding-right: 3px;
+        }
+
+        .radio {
+            margin: 0;
+            padding-left: 130px;
+        }
+
+        #menuSelect {
+            padding: 0;
+        }
+
+        #menuSelect li {
+            display: block;
+            width: 500px;
+            background: #ddf8cc;
+            border: 1px solid #80c605;
+            margin: 1em auto;
+            padding: 0;
+            font-size: 1.3em;
+            text-align: center;
+            list-style-type: none;
+        }
+
+        #menuSelect li:hover {
+            background: #c4faa2;
+        }
+
+        #menuSelect a {
+            display: block;
+            height: 2em;
+            margin: 0px;
+            padding-top: 0.75em;
+            padding-bottom: -0.25em;
+            text-decoration: none;
+        }
+        #content {
+            width: 600px;
+            margin: 0 auto;
+            padding: 0;
+            text-align: left;
+        }
+    </style>
+
+</head>
+
+<body>
+
+<div id="header">
+    <h1>Documents List API Demo</h1>
+    <?php if ($displayMenu === true) { ?>
+        <p><a href="?">Main</a> | <a href="?menu=logout">Logout</a></p>
+    <?php } ?>
+    <div class="clear"></div>
+</div>
+
+<div id="content">
+<?php
+}
+
+/**
+ * Writes the HTML epilogue for this app and exit.
+ *
+ * @param  boolean $displayBackButton (optional) If true, displays a link to go back at the bottom
+ *                                    of the page. Defaults to false.
+ * @return void
+ */
+function endHTML($displayBackButton = false)
+{
+    if ($displayBackButton === true) {
+        echo '<div style="clear: both;">';
+        echo '<a href="javascript:history.go(-1)">&larr; Back</a></div>';
+    }
+?>
+</div>
+</body>
+</html>
+<?php
+exit();
+}
+
+/**
+ * Displays a notice indicating that a login password needs to be
+ * set before continuing.
+ *
+ * @return void
+ */
+function displayPasswordNotSetNotice()
+{
+?>
+    <div class="warning">
+        <h3>Almost there...</h3>
+        <p>Before using this demo, you must set an application password
+            to protect your account. You will also need to set your
+            Google Apps credentials in order to communicate with the Google
+            Apps servers.</p>
+        <p>To continue, open this file in a text editor and fill
+            out the information in the configuration section.</p>
+    </div>
+<?php
+}
+
+/**
+ * Displays a notice indicating that authentication to Google Apps failed.
+ *
+ * @return void
+ */
+function displayAuthenticationFailedNotice()
+{
+?>
+    <div class="warning">
+        <h3>Google Docs Authentication Failed</h3>
+        <p>Authentication with the Google Apps servers failed.</p>
+        <p>Please open this file in a text editor and make
+            sure your credentials are correct.</p>
+    </div>
+<?php
+}
+
+/**
+ * Outputs a request to the user to login to their Google account, including
+ * a link to the AuthSub URL.
+ *
+ * Uses getAuthSubUrl() to get the URL which the user must visit to authenticate
+ *
+ * @param  string $linkText
+ * @return void
+ */
+function requestUserLogin($linkText)
+{
+    $authSubUrl = getAuthSubUrl();
+    echo "<a href=\"{$authSubUrl}\">{$linkText}</a>";
+}
+
+/**
+ * Returns the AuthSub URL which the user must visit to authenticate requests
+ * from this application.
+ *
+ * Uses getCurrentUrl() to get the next URL which the user will be redirected
+ * to after successfully authenticating with the Google service.
+ *
+ * @return string AuthSub URL
+ */
+function getAuthSubUrl()
+{
+    $next = getCurrentUrl();
+    $scope = 'http://docs.google.com/feeds/documents';
+    $secure = false;
+    $session = true;
+    return Zend_Gdata_AuthSub::getAuthSubTokenUri($next, $scope, $secure,
+        $session);
+}
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using AuthSub authentication.
+ *
+ * Uses the $_SESSION['sessionToken'] to store the AuthSub session token after
+ * it is obtained.  The single use token supplied in the URL when redirected
+ * after the user succesfully authenticated to Google is retrieved from the
+ * $_GET['token'] variable.
+ *
+ * @return Zend_Http_Client
+ */
+function getAuthSubHttpClient()
+{
+    global $_SESSION, $_GET;
+    if (!isset($_SESSION['docsSampleSessionToken']) && isset($_GET['token'])) {
+        $_SESSION['docsSampleSessionToken'] =
+            Zend_Gdata_AuthSub::getAuthSubSessionToken($_GET['token']);
+    }
+    $client = Zend_Gdata_AuthSub::getHttpClient($_SESSION['docsSampleSessionToken']);
+    return $client;
+}
+
+/**
+ * Returns the full URL of the current page, based upon env variables
+ *
+ * Env variables used:
+ * $_SERVER['HTTPS'] = (on|off|)
+ * $_SERVER['HTTP_HOST'] = value of the Host: header
+ * $_SERVER['SERVER_PORT'] = port number (only used if not http/80,https/443)
+ * $_SERVER['REQUEST_URI'] = the URI after the method of the HTTP request
+ *
+ * @return string Current URL
+ */
+function getCurrentUrl()
+{
+    global $_SERVER;
+
+    /**
+     * Filter php_self to avoid a security vulnerability.
+     */
+    $php_request_uri = htmlentities(substr($_SERVER['REQUEST_URI'], 0,
+    strcspn($_SERVER['REQUEST_URI'], "\n\r")), ENT_QUOTES);
+
+    if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
+        $protocol = 'https://';
+    } else {
+        $protocol = 'http://';
+    }
+    $host = $_SERVER['HTTP_HOST'];
+    if ($_SERVER['SERVER_PORT'] != '' &&
+        (($protocol == 'http://' && $_SERVER['SERVER_PORT'] != '80') ||
+        ($protocol == 'https://' && $_SERVER['SERVER_PORT'] != '443'))) {
+            $port = ':' . $_SERVER['SERVER_PORT'];
+    } else {
+        $port = '';
+    }
+    return $protocol . $host . $port . $php_request_uri;
+}
+
+/**
+ * Display the main menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayMenu()
+{
+?>
+<h2>Main Menu</h2>
+
+<p>Welcome to the Google Documents List API demo page. Please select
+    from one of the following three options to see a list of commands.</p>
+
+    <ul id="menuSelect">
+        <li><a class="menuSelect" href="?menu=list">List Documents</a></li>
+        <li><a class="menuSelect" href="?menu=query">Query your Documents</a></li>
+        <li><a class="menuSelect" href="?menu=upload">Upload a new document</a></li>
+    </ul>
+
+<p>Tip: You can also run this demo from the command line if your system
+    has PHP CLI support enabled.</p>
+<?php
+}
+
+/**
+ * Log the current user out of the application.
+ *
+ * @return void
+ */
+function logout()
+{
+session_destroy();
+?>
+<h2>Logout</h2>
+
+<p>Logout successful.</p>
+
+<ul id="menuSelect">
+    <li><a class="menuSelect" href="?">Login</a></li>
+</ul>
+<?php
+}
+
+
+/**
+ * Processes loading of this sample code through a web browser.
+ *
+ * @return void
+ */
+function runWWWVersion()
+{
+    session_start();
+
+    // Note that all calls to endHTML() below end script execution!
+
+    global $_SESSION, $_GET;
+    if (!isset($_SESSION['docsSampleSessionToken']) && !isset($_GET['token'])) {
+        requestUserLogin('Please login to your Google Account.');
+    } else {
+        $client = getAuthSubHttpClient();
+        $docs = new Zend_Gdata_Docs($client);
+
+        // First we check for commands that can be submitted either though
+        // POST or GET (they don't make any changes).
+        if (!empty($_REQUEST['command'])) {
+            switch ($_REQUEST['command']) {
+                case 'retrieveAllDocuments':
+                    startHTML();
+                    retrieveAllDocuments($docs, true);
+                    endHTML(true);
+                case 'retrieveWPDocs':
+                    startHTML();
+                    retrieveWPDocs($docs, true);
+                    endHTML(true);
+                case 'retrieveSpreadsheets':
+                    startHTML();
+                    retrieveSpreadsheets($docs, true);
+                    endHTML(true);
+                case 'fullTextSearch':
+                    startHTML();
+                    fullTextSearch($docs, true, $_REQUEST['query']);
+                    endHTML(true);
+                    
+            }
+        }
+    
+        // Now we handle the potentially destructive commands, which have to
+        // be submitted by POST only.
+        if (!empty($_POST['command'])) {
+            switch ($_POST['command']) {
+                case 'uploadDocument':
+                    startHTML();
+                    uploadDocument($docs, true, 
+                        $_FILES['uploadedFile']['name'], 
+                        $_FILES['uploadedFile']['tmp_name']);
+                    endHTML(true);
+                case 'modifySubscription':
+                    if ($_POST['mode'] == 'subscribe') {
+                        startHTML();
+                        endHTML(true);
+                    } elseif ($_POST['mode'] == 'unsubscribe') {
+                        startHTML();
+                        endHTML(true);
+                    } else {
+                        header('HTTP/1.1 400 Bad Request');
+                        startHTML();
+                        echo "<h2>Invalid mode.</h2>\n";
+                        echo "<p>Please check your request and try again.</p>";
+                        endHTML(true);
+                    }
+            }
+        }
+    
+        // Check for an invalid command. If so, display an error and exit.
+        if (!empty($_REQUEST['command'])) {
+            header('HTTP/1.1 400 Bad Request');
+            startHTML();
+            echo "<h2>Invalid command.</h2>\n";
+            echo "<p>Please check your request and try again.</p>";
+            endHTML(true);
+        }
+        // If a menu parameter is available, display a submenu.
+    
+        if (!empty($_REQUEST['menu'])) {
+            switch ($_REQUEST['menu']) {
+                case 'list':
+                    startHTML();
+                    displayListMenu();
+                    endHTML();
+                case 'query':
+                    startHTML();
+                    displayQueryMenu();
+                    endHTML();
+                case 'upload':
+                    startHTML();
+                    displayUploadMenu();
+                    endHTML();
+                case 'logout':
+                    startHTML(false);
+                    logout();
+                    endHTML();
+                default:
+                    header('HTTP/1.1 400 Bad Request');
+                    startHTML();
+                    echo "<h2>Invalid menu selection.</h2>\n";
+                    echo "<p>Please check your request and try again.</p>";
+                    endHTML(true);
+            }
+        }
+        // If we get this far, that means there's nothing to do. Display
+        // the main menu.
+        // If no command was issued and no menu was selected, display the
+        // main menu.
+        startHTML();
+        displayMenu();
+        endHTML();
+    }
+}
+
+/**
+ * Display the menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayListMenu()
+{
+?>
+<h2>List Documents Menu</h2>
+
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Google Documents Feed</h3>
+    <p>Retrieve the feed for all of your documents.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveAllDocuments" />
+    </p>
+    <p><input type="submit" value="Retrieve Documents Feed &rarr;"></p>
+</form>
+
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Google Word Processing Documents</h3>
+    <p>Query the documents list feed for all word processing documents.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveWPDocs" />
+    </p>
+    <p><input type="submit" value="Retrieve Word Processing Documents &rarr;"></p>
+</form>
+
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Google Spreadsheets</h3>
+    <p>Query the documents list feed for all spreadsheets.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveSpreadsheets" />
+    </p>
+    <p><input type="submit" value="Retrieve Spreadsheets &rarr;"></p>
+</form>
+<?php
+}
+
+/**
+ * Display the menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayQueryMenu() 
+{
+?>
+<h2>Query the Documents List Feed</h2>
+
+<form method="get" accept-charset="utf-8">
+    <h3>Search the Documents List Feed</h3>
+    <p>Find documents which contain the desired text.</p>
+    <p>
+        <input type="hidden" name="command" value="fullTextSearch" />
+        <input type="text" name="query" />
+    </p>
+    <p><input type="submit" value="Search Documents Feed &rarr;"></p>
+</form>
+
+<?php
+}
+
+/**
+ * Display the menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayUploadMenu()
+{
+?>
+<h2>Upload a document</h2>
+
+<form method="post" enctype="multipart/form-data">
+    <h3>Select a Document to Upload</h3>
+    <p>Upload a file from your computer to <a href="http://docs.google.com">Google Documents</a>.</p>
+    <p>
+        <input type="hidden" name="command" value="uploadDocument" />
+        <input name="uploadedFile" type="file" />
+    </p>
+    <p><input type="submit" value="Upload the Document &rarr;"></p>
+</form>
+
+<?php
+}
+
+// ************************** PROGRAM ENTRY POINT **************************
+
+if (!isset($_SERVER["HTTP_HOST"]))  {
+    // running through command line
+    runCLIVersion($argv, $argc);
+} else {
+    // running through web server
+    try {
+        runWWWVersion();
+    } catch (Zend_Gdata_Gapps_ServiceException $e) {
+        // Try to recover gracefully from a service exception.
+        // The HTML prologue will have already been sent.
+        echo "<p><strong>Service Error Encountered</strong></p>\n";
+        echo "<pre>" . htmlspecialchars($e->__toString()) . "</pre>";
+        endHTML(true);
+    }
+}

+ 1992 - 0
demos/Zend/Gdata/Gapps.php

@@ -0,0 +1,1992 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the Google Calendar data API.  Utilizes the 
+ * Zend Framework Gdata components to communicate with the Google API.
+ * 
+ * Requires the Zend Framework Gdata components and PHP >= 5.1.4
+ *
+ * You can run this sample both from the command line (CLI) and also
+ * from a web browser.  Run this script without any command line options to 
+ * see usage, eg:
+ *     /usr/bin/env php Gapps.php
+ *
+ * More information on the Command Line Interface is available at:
+ *     http://www.php.net/features.commandline
+ *
+ * When running this code from a web browser, be sure to fill in your 
+ * Google Apps credentials below and choose a password for authentication 
+ * via the web browser.
+ *
+ * Since this is a demo, only minimal error handling and input validation 
+ * are performed. THIS CODE IS FOR DEMONSTRATION PURPOSES ONLY. NOT TO BE 
+ * USED IN A PRODUCTION ENVIRONMENT.
+ *
+ * NOTE: You must ensure that Zend Framework is in your PHP include
+ * path.  You can do this via php.ini settings, or by modifying the 
+ * argument to set_include_path in the code below.
+ */
+
+// ************************ BEGIN WWW CONFIGURATION ************************
+
+/**
+ * Google Apps username. This is the username (without domain) used 
+ * to administer your Google Apps account. This value is only 
+ * used when accessing this demo on a web server.
+ * 
+ * For example, if you login to Google Apps as 'foo@bar.com.inavlid', 
+ * your username is 'foo'.
+ */
+define('GAPPS_USERNAME', 'username');
+
+/**
+ * Google Apps domain. This is the domain associated with your 
+ * Google Apps account. This value is only used when accessing this demo 
+ * on a web server.
+ * 
+ * For example, if you login to Google Apps as foo@bar.com.inavlid, 
+ * your domain is 'bar.com.invalid'.
+ */
+define('GAPPS_DOMAIN', 'example.com.invalid');
+
+/**
+ * Google Apps password. This is the password associated with the above 
+ * username. This value is only used when accessing this demo on a 
+ * web server.
+ */
+define('GAPPS_PASSWORD', 'your password here');
+
+/**
+ * Login password. This password is used to protect your account from 
+ * unauthorized access when running this demo on a web server.
+ *
+ * If this field is blank, all access will be denied. A blank password 
+ * field is not the same as no password (which is disallowed for 
+ * security reasons).
+ *
+ * NOTE: While we could technically just ask the user for their Google Apps
+ *       credentials, the ClientLogin API is not intended for direct use by
+ *       web applications. If you are the only user of the application, this
+ *       is fine--- but you should not ask other users to enter their 
+ *       credentials via your web application.
+ */
+define('LOGIN_PASSWORD', '');
+
+// ************************* END WWW CONFIGURATION *************************
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+/**
+ * @see Zend_Gdata_Gapps
+ */
+Zend_Loader::loadClass('Zend_Gdata_Gapps');
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using the ClientLogin credentials supplied.
+ *
+ * @param  string $user The username, in e-mail address format, to authenticate
+ * @param  string $pass The password for the user specified
+ * @return Zend_Http_Client
+ */
+function getClientLoginHttpClient($user, $pass) 
+{
+  $service = Zend_Gdata_Gapps::AUTH_SERVICE_NAME;
+  $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service);
+  return $client;
+}
+
+/**
+ * Creates a new user for the current domain. The user will be created 
+ * without admin privileges.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps      The service object to use for communicating with the Google
+ *                                      Apps server.
+ * @param  boolean          $html       True if output should be formatted for display in a web browser.
+ * @param  string           $username   The desired username for the user.
+ * @param  string           $givenName  The given name for the user.
+ * @param  string           $familyName The family name for the user.
+ * @param  string           $password   The plaintext password for the user.
+ * @return void
+ */
+function createUser($gapps, $html, $username, $givenName, $familyName, 
+        $password)
+{
+    if ($html) {echo "<h2>Create User</h2>\n";}
+    $gapps->createUser($username, $givenName, $familyName, 
+        $password);
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Retrieves a user for the current domain by username. Information about 
+ * that user is then output.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The desired username for the user.
+ * @return void
+ */
+function retrieveUser($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>User Information</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($html) {echo '<p>';}
+    
+    if ($user !== null) {
+        echo '             Username: ' . $user->login->username;
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo '           Given Name: ';
+        if ($html) {
+            echo htmlspecialchars($user->name->givenName);
+        } else {
+            echo $user->name->givenName;
+        }
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo '          Family Name: ';
+        if ($html) {
+            echo htmlspecialchars($user->name->familyName);
+        } else {
+            echo $user->name->familyName;
+        }
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo '            Suspended: ' . ($user->login->suspended ? 'Yes' : 'No');
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo '                Admin: ' . ($user->login->admin ? 'Yes' : 'No');
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo ' Must Change Password: ' . 
+            ($user->login->changePasswordAtNextLogin ? 'Yes' : 'No');
+        if ($html) {echo '<br />';}
+        echo "\n";
+        
+        echo '  Has Agreed To Terms: ' . 
+            ($user->login->agreedToTerms ? 'Yes' : 'No');
+        
+    } else {
+        echo 'Error: Specified user not found.';
+    }
+    if ($html) {echo '</p>';}
+    echo "\n";
+}
+
+/**
+ * Retrieves the list of users for the current domain and outputs 
+ * that list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps The service object to use for communicating with the Google Apps server.
+ * @param  boolean          $html  True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveAllUsers($gapps, $html)
+{
+    if ($html) {echo "<h2>Registered Users</h2>\n";}
+    
+    $feed = $gapps->retrieveAllUsers();
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $user) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $user->login->username . ' (';
+        if ($html) {
+            echo htmlspecialchars($user->name->givenName . ' ' . 
+                $user->name->familyName);
+        } else {
+            echo $user->name->givenName . ' ' . $user->name->familyName;
+        }
+        echo ')';
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Change the name for an existing user.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps         The service object to use for communicating with the Google
+ *                                         Apps server.
+ * @param  boolean          $html          True if output should be formatted for display in a web browser.
+ * @param  string           $username      The username which should be updated
+ * @param  string           $newGivenName  The new given name for the user.
+ * @param  string           $newFamilyName The new family name for the user.
+ * @return void
+ */
+function updateUserName($gapps, $html, $username, $newGivenName, $newFamilyName)
+{
+    if ($html) {echo "<h2>Update User Name</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->name->givenName = $newGivenName;
+        $user->name->familyName = $newFamilyName;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Change the password for an existing user.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps       The service object to use for communicating with the Google
+ *                                       Apps server.
+ * @param  boolean          $html        True if output should be formatted for display in a web browser.
+ * @param  string           $username    The username which should be updated
+ * @param  string           $newPassword The new password for the user.
+ * @return void
+ */
+function updateUserPassword($gapps, $html, $username, $newPassword)
+{
+    if ($html) {echo "<h2>Update User Password</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->password = $newPassword;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Suspend a given user. The user will not be able to login until restored.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function suspendUser($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Suspend User</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->suspended = true;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Restore a given user after being suspended.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function restoreUser($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Restore User</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->suspended = false;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Give a user admin rights.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function giveUserAdminRights($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Grant Administrative Rights</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->admin = true;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Revoke a user's admin rights.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function revokeUserAdminRights($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Revoke Administrative Rights</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->admin = false;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Force a user to change their password at next login.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function setUserMustChangePassword($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Force User To Change Password</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->changePasswordAtNextLogin = true;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Undo forcing a user to change their password at next login.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be updated.
+ * @return void
+ */
+function clearUserMustChangePassword($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Undo Force User To Change Password</h2>\n";}
+    
+    $user = $gapps->retrieveUser($username);
+    
+    if ($user !== null) {
+        $user->login->changePasswordAtNextLogin = false;
+        $user->save();
+    } else {
+        if ($html) {echo '<p>';}
+        echo 'Error: Specified user not found.';
+        if ($html) {echo '</p>';}
+        echo "\n";
+    }
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Delete the user who owns a given username.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username which should be deleted.
+ * @return void
+ */
+function deleteUser($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Delete User</h2>\n";}
+    
+    $gapps->deleteUser($username);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Create a new nickname.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username to which the nickname should be assigned.
+ * @param  string           $nickname The name of the nickname to be created.
+ * @return void
+ */
+function createNickname($gapps, $html, $username, $nickname)
+{
+    if ($html) {echo "<h2>Create Nickname</h2>\n";}
+    
+    $gapps->createNickname($username, $nickname);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Retrieve a specified nickname and output its ownership information.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $nickname The name of the nickname to be retrieved.
+ * @return void
+ */
+function retrieveNickname($gapps, $html, $nickname)
+{
+    if ($html) {echo "<h2>Nickname Information</h2>\n";}
+    
+    $nickname = $gapps->retrieveNickname($nickname);
+    
+    if ($html) {echo '<p>';}
+    
+    if ($nickname !== null) {
+        echo ' Nickname: ' . $nickname->nickname->name;
+        if ($html) {echo '<br />';}
+        echo "\n";
+    
+        echo '    Owner: ' . $nickname->login->username;
+    } else {
+        echo 'Error: Specified nickname not found.';
+    }
+    if ($html) {echo '</p>';}
+    echo "\n";
+}
+
+/**
+ * Outputs all nicknames owned by a specific username.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $username The username whose nicknames should be displayed.
+ * @return void
+ */
+function retrieveNicknames($gapps, $html, $username)
+{
+    if ($html) {echo "<h2>Registered Nicknames For {$username}</h2>\n";}
+    
+    $feed = $gapps->retrieveNicknames($username);
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $nickname) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $nickname->nickname->name;
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+
+/**
+ * Retrieves the list of nicknames for the current domain and outputs 
+ * that list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps The service object to use for communicating with the Google
+ *                                 Apps server.
+ * @param  boolean          $html  True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveAllNicknames($gapps, $html)
+{
+    if ($html) {echo "<h2>Registered Nicknames</h2>\n";}
+    
+    $feed = $gapps->retrieveAllNicknames();
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $nickname) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $nickname->nickname->name . ' => ' . $nickname->login->username;
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Delete's a specific nickname from the current domain.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps    The service object to use for communicating with the Google
+ *                                    Apps server.
+ * @param  boolean          $html     True if output should be formatted for display in a web browser.
+ * @param  string           $nickname The nickname that should be deleted.
+ * @return void
+ */
+function deleteNickname($gapps, $html, $nickname)
+{
+    if ($html) {echo "<h2>Delete Nickname</h2>\n";}
+    
+    $gapps->deleteNickname($nickname);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+    
+}
+
+/**
+ * Create a new email list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps     The service object to use for communicating with the Google
+ *                                     Apps server.
+ * @param  boolean          $html      True if output should be formatted for display in a web browser.
+ * @param  string           $emailList The name of the email list to be created.
+ * @return void
+ */
+function createEmailList($gapps, $html, $emailList)
+{
+    if ($html) {echo "<h2>Create Email List</h2>\n";}
+    
+    $gapps->createEmailList($emailList);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Outputs the list of email lists to which the specified address is 
+ * subscribed.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps     The service object to use for communicating with the Google
+ *                                     Apps server.
+ * @param  boolean          $html      True if output should be formatted for display in a web browser.
+ * @param  string           $recipient The email address of the recipient whose subscriptions should
+ *                                     be retrieved. Only a username is required if the recipient is a
+ *                                     member of the current domain.
+ * @return void
+ */
+function retrieveEmailLists($gapps, $html, $recipient)
+{
+    if ($html) {echo "<h2>Email List Subscriptions For {$recipient}</h2>\n";}
+    
+    $feed = $gapps->retrieveEmailLists($recipient);
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $list) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $list->emailList->name;
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Outputs the list of all email lists on the current domain.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps The service object to use for communicating with the Google
+ *                                 Apps server.
+ * @param  boolean          $html  True if output should be formatted for display in a web browser.
+ * @return void
+ */
+function retrieveAllEmailLists($gapps, $html)
+{
+    if ($html) {echo "<h2>Registered Email Lists</h2>\n";}
+    
+    $feed = $gapps->retrieveAllEmailLists();
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $list) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $list->emailList->name;
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Delete's a specific email list from the current domain.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps     The service object to use for communicating with the Google
+ *                                     Apps server.
+ * @param  boolean          $html      True if output should be formatted for display in a web browser.
+ * @param  string           $emailList The email list that should be deleted.
+ * @return void
+ */
+function deleteEmailList($gapps, $html, $emailList)
+{
+    if ($html) {echo "<h2>Delete Email List</h2>\n";}
+    
+    $gapps->deleteEmailList($emailList);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Add a recipient to an existing email list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps            The service object to use for communicating with the
+ *                                            Google Apps server.
+ * @param  boolean          $html             True if output should be formatted for display in a
+ *                                            web browser.
+ * @param  string           $recipientAddress The address of the recipient who should be added.
+ * @param  string           $emailList        The name of the email address the recipient be added to.
+ * @return void
+ */
+function addRecipientToEmailList($gapps, $html, $recipientAddress, 
+        $emailList)
+{
+    if ($html) {echo "<h2>Subscribe Recipient</h2>\n";}
+    
+    $gapps->addRecipientToEmailList($recipientAddress, $emailList);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+}
+
+/**
+ * Outputs the list of all recipients for a given email list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps     The service object to use for communicating with the Google
+ *                                     Apps server.
+ * @param  boolean          $html      True if output should be formatted for display in a web browser.
+ * @param  string           $emailList The email list whose recipients should be output.
+ * @return void
+ */
+function retrieveAllRecipients($gapps, $html, $emailList)
+{
+    if ($html) {echo "<h2>Email List Recipients For {$emailList}</h2>\n";}
+    
+    $feed = $gapps->retrieveAllRecipients($emailList);
+    
+    if ($html) {echo "<ul>\n";}
+    
+    foreach ($feed as $recipient) {
+        if ($html) {
+            echo "  <li>";
+        } else {
+            echo "  * ";
+        }
+        echo $recipient->who->email;
+        if ($html) {echo '</li>';}
+        echo "\n";
+    }
+    if ($html) {echo "</ul>\n";}
+}
+
+/**
+ * Remove an existing recipient from an email list.
+ * 
+ * @param  Zend_Gdata_Gapps $gapps            The service object to use for communicating with the
+ *                                            Google Apps server.
+ * @param  boolean          $html             True if output should be formatted for display in a
+ *                                            web browser.
+ * @param  string           $recipientAddress The address of the recipient who should be removed.
+ * @param  string           $emailList        The email list from which the recipient should be removed.
+ * @return void
+ */
+function removeRecipientFromEmailList($gapps, $html, $recipientAddress,
+        $emailList)
+{
+    if ($html) {echo "<h2>Unsubscribe Recipient</h2>\n";}
+    
+    $gapps->removeRecipientFromEmailList($recipientAddress, $emailList);
+    
+    if ($html) {echo "<p>Done.</p>\n";}
+    
+}
+
+// ************************ BEGIN CLI SPECIFIC CODE ************************
+
+/**
+ * Display list of valid commands.
+ * 
+ * @param  string $executable The name of the current script. This is usually available as $argv[0].
+ * @return void
+ */
+function displayHelp($executable)
+{
+    echo "Usage: php {$executable} <action> [<username>] [<password>] " .
+        "[<arg1> <arg2> ...]\n\n";
+    echo "Possible action values include:\n" .
+        "createUser\n" .
+        "retrieveUser\n" .
+        "retrieveAllUsers\n" .
+        "updateUserName\n" .
+        "updateUserPassword\n" .
+        "suspendUser\n" .
+        "restoreUser\n" .
+        "giveUserAdminRights\n" .
+        "revokeUserAdminRights\n" .
+        "setUserMustChangePassword\n" .
+        "clearUserMustChangePassword\n" .
+        "deleteUser\n" .
+        "createNickname\n" .
+        "retrieveNickname\n" .
+        "retrieveNicknames\n" .
+        "retrieveAllNicknames\n" .
+        "deleteNickname\n" .
+        "createEmailList\n" .
+        "retrieveEmailLists\n" .
+        "retrieveAllEmailLists\n" .
+        "deleteEmailList\n" .
+        "addRecipientToEmailList\n" .
+        "retrieveAllRecipients\n" .
+        "removeRecipientFromEmailList\n";
+}
+
+/**
+ * Parse command line arguments and execute appropriate function when 
+ * running from the command line.
+ *
+ * If no arguments are provided, usage information will be provided.
+ * 
+ * @param  array   $argv    The array of command line arguments provided by PHP.
+ *                 $argv[0] should be the current executable name or '-' if not available.
+ * @param  integer $argc    The size of $argv.
+ * @return void
+ */
+function runCLIVersion($argv, $argc)
+{
+    if (isset($argc) && $argc >= 2) {
+        # Prepare a server connection
+        if ($argc >= 5) {
+            try {
+                $client = getClientLoginHttpClient($argv[2] . '@' . $argv[3], $argv[4]);
+                $gapps = new Zend_Gdata_Gapps($client, $argv[3]);
+            } catch (Zend_Gdata_App_AuthException $e) {
+                echo "Error: Unable to authenticate. Please check your credentials.\n";
+                exit(1);
+            }
+        }
+    
+        # Dispatch arguments to the desired method
+        switch ($argv[1]) {
+            case 'createUser':
+                if ($argc == 9) {
+                    createUser($gapps, false, $argv[5], $argv[6], $argv[7], $argv[8]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username> <given name> <family name> <user's password>\n\n";
+                    echo "This creates a new user with the given username.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe John Doe p4ssw0rd\n";
+                }
+                break;
+            case 'retrieveUser':
+                if ($argc == 6) {
+                    retrieveUser($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "This retrieves the user with the specified " . 
+                        "username and displays information about that user.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'retrieveAllUsers':
+                if ($argc == 5) {
+                    retrieveAllUsers($gapps, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "\n\n";
+                    echo "This lists all users on the current domain.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password>\n";
+                }
+                break;
+            case 'updateUserName':
+                if ($argc == 8) {
+                    updateUserName($gapps, false, $argv[5], $argv[6], $argv[7]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username> <new given name> <new family name>\n\n";
+                    echo "Renames an existing user.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe Jane Doe\n";
+                }
+                break;
+            case 'updateUserPassword':
+                if ($argc == 7) {
+                    updateUserPassword($gapps, false, $argv[5], $argv[6]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username> <new user password>\n\n";
+                    echo "Changes the password for an existing user.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe password1\n";
+                }
+                break;
+            case 'suspendUser':
+                if ($argc == 6) {
+                    suspendUser($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "This suspends the given user.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'restoreUser':
+                if ($argc == 6) {
+                    restoreUser($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "This restores the given user after being suspended.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'giveUserAdminRights':
+                if ($argc == 6) {
+                    giveUserAdminRights($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Give a user admin rights for this domain.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'revokeUserAdminRights':
+                if ($argc == 6) {
+                    revokeUserAdminRights($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Remove a user's admin rights for this domain.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'setUserMustChangePassword':
+                if ($argc == 6) {
+                    setUserMustChangePassword($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Force a user to change their password at next login.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'clearUserMustChangePassword':
+                if ($argc == 6) {
+                    clearUserMustChangePassword($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Clear the flag indicating that a user must change " . 
+                        "their password at next login.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'deleteUser':
+                if ($argc == 6) {
+                    deleteUser($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Delete the user who owns a given username.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'createNickname':
+                if ($argc == 7) {
+                    createNickname($gapps, false, $argv[5], $argv[6]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username> <nickname>\n\n";
+                    echo "Create a new nickname for the specified user.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe johnny\n";
+                }
+                break;
+            case 'retrieveNickname':
+                if ($argc == 6) {
+                    retrieveNickname($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<nickname>\n\n";
+                    echo "Retrieve a nickname and display its ownership " . 
+                        "information.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "johnny\n";
+                }
+                break;
+            case 'retrieveNicknames':
+                if ($argc == 6) {
+                    retrieveNicknames($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<user's username>\n\n";
+                    echo "Output all nicknames owned by a specific username.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "jdoe\n";
+                }
+                break;
+            case 'retrieveAllNicknames':
+                if ($argc == 5) {
+                    retrieveAllNicknames($gapps, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "\n\n";
+                    echo "Output all registered nicknames on the system.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "\n";
+                }
+                break;
+            case 'deleteNickname':
+                if ($argc == 6) {
+                    deleteNickname($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<nickname>\n\n";
+                    echo "Delete a specific nickname.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "johnny\n";
+                }
+                break;
+            case 'createEmailList':
+                if ($argc == 6) {
+                    createEmailList($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<email list>\n\n";
+                    echo "Create a new email list with the specified name.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "friends\n";
+                }
+                break;
+            case 'retrieveEmailLists':
+                if ($argc == 6) {
+                    retrieveEmailLists($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<recipient>\n\n";
+                    echo "Retrieve all email lists to which the specified " . 
+                        "address is subscribed.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "johnny@somewhere.com.invalid\n";
+                }
+                break;
+            case 'retrieveAllEmailLists':
+                if ($argc == 5) {
+                    retrieveAllEmailLists($gapps, false);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "\n\n";
+                    echo "Retrieve a list of all email lists on the current " . 
+                        "domain.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "\n";
+                }
+                break;
+            case 'deleteEmailList':
+                if ($argc == 6) {
+                    deleteEmailList($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<email list>\n\n";
+                    echo "Delete a specified email list.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "friends\n";
+                }
+                break;
+            case 'addRecipientToEmailList':
+                if ($argc == 7) {
+                    addRecipientToEmailList($gapps, false, $argv[5], $argv[6]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<recipient> <email list>\n\n";
+                    echo "Add a recipient to an existing email list.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "johnny@somewhere.com.invalid friends\n";
+                }
+                break;
+            case 'retrieveAllRecipients':
+                if ($argc == 6) {
+                    retrieveAllRecipients($gapps, false, $argv[5]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<email list>\n\n";
+                    echo "Retrieve all recipients for an existing email list.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "friends\n";
+                }
+                break;
+            case 'removeRecipientFromEmailList':
+                if ($argc == 7) {
+                    removeRecipientFromEmailList($gapps, false, $argv[5], $argv[6]);
+                } else {
+                    echo "Usage: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "<recipient> <email list>\n\n";
+                    echo "Remove an existing recipient from an email list.\n";
+                    echo "EXAMPLE: php {$argv[0]} {$argv[1]} <username> <domain> <password> " . 
+                        "johnny@somewhere.com.invalid friends\n";
+                }
+                break;
+            default:
+                // Invalid action entered
+                displayHelp($argv[0]);
+        // End switch block
+        }
+    } else {
+        // action left unspecified
+        displayHelp($argv[0]);
+    }
+}
+
+// ************************ BEGIN WWW SPECIFIC CODE ************************
+
+/**
+ * Writes the HTML prologue for this app.
+ *
+ * NOTE: We would normally keep the HTML/CSS markup separate from the business
+ *       logic above, but have decided to include it here for simplicity of
+ *       having a single-file sample.
+ *
+ *
+ * @param  boolean $displayMenu (optional) If set to true, a navigation 
+ *                              menu is displayed at the top of the page. Default is true.
+ * @return void
+ */
+function startHTML($displayMenu = true)
+{
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+    <title>Google Apps Provisioning API Demo</title>
+
+    <style type="text/css" media="screen">
+        body {
+            font-family: Arial, Helvetica, sans-serif;
+            font-size: small;
+        }
+    
+        #header {
+            background-color: #9cF;
+            -moz-border-radius: 5px;
+            -webkit-border-radius: 5px;
+            padding-left: 5px;
+            height: 2.4em;
+        }
+        
+        #header h1 {
+            width: 49%;
+            display: inline;
+            float: left;
+            margin: 0;
+            padding: 0;
+            font-size: 2em;
+        }
+        
+        #header p {
+            width: 49%;
+            margin: 0;
+            padding-right: 15px;
+            float: right;
+            line-height: 2.4em;
+            text-align: right;
+        }
+        
+        .clear {
+            clear:both;
+        }
+        
+        h2 {
+            background-color: #ccc;
+            -moz-border-radius: 5px;
+            -webkit-border-radius: 5px;
+            margin-top: 1em;
+            padding-left: 5px;
+        }
+        
+        .error {
+            color: red;
+        }
+        
+        form {
+            width: 500px;
+            background: #ddf8cc;
+            border: 1px solid #80c605;
+            padding: 0 1em;
+            margin: 1em auto;
+        }
+        
+        .warning {
+            width: 500px;
+            background: #F4B5B4;
+            border: 1px solid #900;
+            padding: 0 1em;
+            margin: 1em auto;
+        }
+        
+        label {
+            display: block;
+            width: 130px;
+            float: left;
+            text-align: right;
+            padding-top: 0.3em;
+            padding-right: 3px;
+        }
+        
+        .radio {
+            margin: 0;
+            padding-left: 130px;
+        }
+        
+        #menuSelect {
+            padding: 0;
+        }
+        
+        #menuSelect li {
+            display: block;
+            width: 500px;
+            background: #ddf8cc;
+            border: 1px solid #80c605;
+            margin: 1em auto;
+            padding: 0;
+            font-size: 1.3em;
+            text-align: center;
+            list-style-type: none;
+        }
+        
+        #menuSelect li:hover {
+            background: #c4faa2;
+        }
+        
+        #menuSelect a {
+            display: block;
+            height: 2em;
+            margin: 0px;
+            padding-top: 0.75em;
+            padding-bottom: -0.25em;
+            text-decoration: none;
+        }
+        
+        #content {
+            width: 600px;
+            margin: 0 auto;
+            padding: 0;
+            text-align: left;
+        }
+    </style>
+
+</head>
+
+<body>
+
+<div id="header">
+    <h1>Google Apps API Demo</h1>
+    <?php if ($displayMenu === true) { ?>
+        <p><?php echo GAPPS_DOMAIN ?> | <a href="?">Main</a> | <a href="?menu=logout">Logout</a></p>
+    <?php } ?>
+    <div class="clear"></div>
+</div>
+
+<div id="content">
+<?php
+}
+
+/**
+ * Writes the HTML epilogue for this app and exit.
+ *
+ * @param  boolean $displayBackButton (optional) If true, displays a 
+ *                                    link to go back at the bottom of the page. Defaults to false.
+ * @return void
+ */
+function endHTML($displayBackButton = false)
+{
+    if ($displayBackButton === true) {
+        echo '<a href="javascript:history.go(-1)">&larr; Back</a>';
+    }
+?>
+</div>
+</body>
+</html>
+<?php
+exit();
+}
+
+/**
+ * Displays a notice indicating that a login password needs to be 
+ * set before continuing.
+ *
+ * @return void
+ */
+function displayPasswordNotSetNotice()
+{
+?>
+    <div class="warning">
+        <h3>Almost there...</h3>
+        <p>Before using this demo, you must set an application password 
+            to protect your account. You will also need to set your 
+            Google Apps credentials in order to communicate with the Google 
+            Apps servers.</p>
+        <p>To continue, open this file in a text editor and fill 
+            out the information in the configuration section.</p>
+    </div>
+<?php
+}
+
+/**
+ * Displays a notice indicating that authentication to Google Apps failed.
+ *
+ * @return void
+ */
+function displayAuthenticationFailedNotice()
+{
+?>
+    <div class="warning">
+        <h3>Google Apps Authentication Failed</h3>
+        <p>Authentication with the Google Apps servers failed.</p>
+        <p>Please open this file in a text editor and make 
+            sure your credentials are correct.</p>
+    </div>
+<?php
+}
+
+/**
+ * Outputs a request to the user to enter their login password.
+ *
+ * @param  string $errorText (optional) Error text to be displayed next to the login form.
+ * @return void
+ */
+function requestUserLogin($errorText = null)
+{
+?>
+    <form method="post" accept-charset="utf-8">
+        <h3>Authentication Required</h3>
+        <?php
+            if ($errorText !== null) {
+                echo '<span class="error">' . $errorText . "</span>\n";
+            }
+        ?>
+        <p>Please enter your login password to continue.</p>
+        <p><label for="password">Password: </label>
+             <input type="password" name="password" value="" /></p>
+        <p><strong>Notice:</strong> This application is for demonstration 
+            purposes only. Not for use in a production environment.</p>
+        <p><input type="submit" value="Continue &rarr;"></p>
+    </form>
+<?php
+}
+
+/**
+ * Display the main menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayMenu()
+{
+?>
+<h2>Main Menu</h2>
+
+<p>Welcome to the Google Apps Provisioning API demo page. Please select 
+    from one of the following three options to see a list of commands.</p>
+    
+    <ul id="menuSelect">
+        <li><a class="menuSelect" href="?menu=user">User Maintenance Menu</a></li>
+        <li><a class="menuSelect" href="?menu=nickname">Nickname Maintenance Menu</a></li>
+        <li><a class="menuSelect" href="?menu=emailList">Email List Maintenance Menu</a></li>
+    </ul>
+    
+<p>Tip: You can also run this demo from the command line if your system 
+    has PHP CLI support enabled.</p>
+<?php
+}
+
+/**
+ * Display the user maintenance menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayUserMenu()
+{
+?>
+<h2>User Maintenance Menu</h2>
+
+<form method="post" accept-charset="utf-8">
+    <h3>Create User</h3>
+    <p>Create a new user with the given properties.</p>
+    <p>
+        <input type="hidden" name="command" value="createUser" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+        <label for="givenName">Given Name: </label>
+        <input type="text" name="givenName" value="" /><br />
+        <label for="familyName">Family Name: </label>
+        <input type="text" name="familyName" value="" /><br />
+        <label for="pass">Password: </label>
+        <input type="password" name="pass" value="" />
+    </p>
+
+    <p><input type="submit" value="Create User &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve User</h3>
+    <p>Retrieve the information for an existing user.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveUser" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Retrieve User &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve All Users</h3>
+    <p>Retrieve the list of all users on the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveAllUsers" />
+    </p>
+
+    <p><input type="submit" value="Retrieve Users &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Update Name</h3>
+    <p>Update the name for an existing user.</p>
+    <p>
+        <input type="hidden" name="command" value="updateUserName" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+        <label for="givenName">New Given Name: </label>
+        <input type="text" name="givenName" value="" /><br />
+        <label for="familyName">New Family Name: </label>
+        <input type="text" name="familyName" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Update User &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Update Password</h3>
+    <p>Update the password for an existing user.</p>
+    <p>
+        <input type="hidden" name="command" value="updateUserPassword" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+        <label for="pass">New Password: </label>
+        <input type="password" name="pass" value="" /></p>
+    </p>
+
+    <p><input type="submit" value="Update User &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Suspend/Restore User</h3>
+    <p>Mark an existing user as suspended or restore a suspended user. 
+        While suspended, the user will be prohibited from logging into 
+        this domain.</p>
+    <p>
+        <input type="hidden" name="command" value="setUserSuspended" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" />
+    </p>
+    <div class="radio">
+        <input type="radio" name="mode" value="restore">User may log into 
+            this domain.</input><br />
+        <input type="radio" name="mode" value="suspend" checked="true">User 
+            may <strong>not</strong> log into this domain.</input>
+    </div>
+
+    <p><input type="submit" value="Update User &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Issue/Revoke Admin Rights</h3>
+    <p>Set whether an existing user has administrative rights for the current
+         domain.</p>
+    <p>
+        <input type="hidden" name="command" value="setUserAdmin" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" />
+    </p>
+    <div class="radio">
+        <input type="radio" name="mode" value="issue">User 
+            may administer this domain.</input><br />
+        <input type="radio" name="mode" value="revoke" checked="true">User 
+            may <strong>not</strong> administer this domain.</input>
+    </div>
+
+    <p><input type="submit" value="Update User &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Force User To Change Password</h3>
+    <p>Set whether an existing user must change their password at 
+        their next login.</p>
+    <p>
+        <input type="hidden" name="command" value="setForceChangePassword" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" />
+    </p>
+    <div class="radio">
+        <input type="radio" name="mode" value="set">User is required to 
+            change their password at next login.</input><br />
+        <input type="radio" name="mode" value="clear" checked="true">User is 
+            <strong>not</strong> required to change their password at next 
+                login.</input>
+    </div>
+
+    <p><input type="submit" value="Update User &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Delete User</h3>
+    <p>Delete an existing user on the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="deleteUser" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Delete User &rarr;"></p>
+</form>
+<?php
+}
+
+/**
+ * Display the nickname maintenance menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayNicknameMenu()
+{
+?>
+<h2>Nickname Maintenance Menu</h2>
+
+<form method="post" accept-charset="utf-8">
+    <h3>Create Nickname</h3>
+    <p>Create a nickname for an existing user.</p>
+    <p>
+        <input type="hidden" name="command" value="createNickname" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+        <label for="nickname">Nickname: </label>
+        <input type="text" name="nickname" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Create Nickname &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Nickname</h3>
+    <p>Retrieve the information for an existing nickname.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveNickname" />
+        <label for="nickname">Nickname: </label>
+        <input type="text" name="nickname" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Retrieve Nickname &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Nicknames</h3>
+    <p>Retrieve the nicknames associated with an existing username.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveNicknames" />
+        <label for="user">Username: </label>
+        <input type="text" name="user" value="" /><br />
+    </p>
+    
+    <p><input type="submit" value="Retrieve Nicknames &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve All Nicknames</h3>
+    <p>Retrieve the nicknames on the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveAllNicknames" />
+    </p>
+
+    <p><input type="submit" value="Retrieve Nicknames &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Delete Nickname</h3>
+    <p>Delete an existing nickname from the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="deleteNickname" />
+        <label for="nickname">Nickname: </label>
+        <input type="text" name="nickname" value="" /><br />
+    </p>
+    
+    <p><input type="submit" value="Delete Nickname &rarr;"></p>
+</form>
+<?php
+}
+
+/**
+ * Display the email list maintenance menu for running in a web browser.
+ *
+ * @return void
+ */
+function displayEmailListMenu()
+{
+?>
+<h2>Email List Maintenance Menu</h2>
+
+<form method="post" accept-charset="utf-8">
+    <h3>Create Email List</h3>
+    <p>Create a new email list for the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="createEmailList" />
+        <label for="emailList">List Name: </label>
+        <input type="text" name="emailList" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Create List &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve Email Lists</h3>
+    <p>Retrieve all email lists to which a given email address is 
+        subscribed.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveEmailLists" />
+        <label for="recipient">Recipient Address: </label>
+        <input type="text" name="recipient" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Retrieve Lists &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve All Email Lists</h3>
+    <p>Retrieve all email lists on the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveAllEmailLists" />
+    </p>
+
+    <p><input type="submit" value="Retrieve Lists &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Delete Email List</h3>
+    <p>Delete an existing email list from the current domain.</p>
+    <p>
+        <input type="hidden" name="command" value="deleteEmailList" />
+        <label for="emailList">List Name: </label>
+        <input type="text" name="emailList" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Delete List &rarr;"></p>
+</form>
+<form method="post" accept-charset="utf-8">
+    <h3>Add Recipient To Email List</h3>
+    <p>Add or remove a recipient from an existing email list. A complete 
+        email address is required for recipients outside the current 
+        domain.</p>
+    <p>
+        <input type="hidden" name="command" value="modifySubscription" />
+        <label for="emailList">List Name: </label>
+        <input type="text" name="emailList" value="" /><br />
+        <label for="recipient">Recipient Address: </label>
+        <input type="text" name="recipient" value="" /><br />
+        <div class="radio">
+            <input type="radio" name="mode" value="subscribe">Subscribe 
+                recipient.</input><br />
+            <input type="radio" name="mode" value="unsubscribe" 
+                checked="true">Unsubscribe recipient.</input>
+        </div>
+    </p>
+
+    <p><input type="submit" value="Update Subscription &rarr;"></p>
+</form>
+<form method="get" accept-charset="utf-8">
+    <h3>Retrieve All Recipients</h3>
+    <p>Retrieve all recipients subscribed to an existing email list.</p>
+    <p>
+        <input type="hidden" name="command" value="retrieveAllRecipients" />
+        <label for="emailList">List Name: </label>
+        <input type="text" name="emailList" value="" /><br />
+    </p>
+
+    <p><input type="submit" value="Retrieve Recipients &rarr;"></p>
+</form>
+<?php
+}
+
+/**
+ * Log the current user out of the application.
+ *
+ * @return void
+ */
+function logout()
+{
+session_destroy();
+?>
+<h2>Logout</h2>
+
+<p>Logout successful.</p>
+
+<ul id="menuSelect">
+    <li><a class="menuSelect" href="?">Login</a></li>
+</ul>
+<?php
+}
+
+
+/**
+ * Processes loading of this sample code through a web browser.
+ *
+ * @return void
+ */
+function runWWWVersion() 
+{
+    session_start();
+    
+    // Note that all calls to endHTML() below end script execution!
+
+    // Check to make sure that the user has set a password.
+    $p = LOGIN_PASSWORD;
+    if (empty($p)) {
+        startHTML(false);
+        displayPasswordNotSetNotice();
+        endHTML();
+    }
+    
+    // Grab any login credentials that might be waiting in the request
+    if (!empty($_POST['password'])) {
+        if ($_POST['password'] == LOGIN_PASSWORD) {
+            $_SESSION['authenticated'] = 'true';
+        } else {
+            // Invalid password. Stop and display a login screen.
+            startHTML(false);
+            requestUserLogin("Incorrect password.");
+            endHTML();
+        }
+    }
+    
+    // If the user isn't authenticated, display a login screen
+    if (!isset($_SESSION['authenticated'])) {
+        startHTML(false);
+        requestUserLogin();
+        endHTML();
+    }
+    
+    // Try to login. If login fails, log the user out and display an 
+    // error message.
+    try {
+        $client = getClientLoginHttpClient(GAPPS_USERNAME . '@' . 
+            GAPPS_DOMAIN, GAPPS_PASSWORD);
+        $gapps = new Zend_Gdata_Gapps($client, GAPPS_DOMAIN);
+    } catch (Zend_Gdata_App_AuthException $e) {
+        session_destroy();
+        startHTML(false);
+        displayAuthenticationFailedNotice();
+        endHTML();
+    }
+    
+    // Success! We're logged in.
+    // First we check for commands that can be submitted either though 
+    // POST or GET (they don't make any changes).
+    if (!empty($_REQUEST['command'])) {
+        switch ($_REQUEST['command']) {
+            case 'retrieveUser':
+                startHTML();
+                retrieveUser($gapps, true, $_REQUEST['user']);
+                endHTML(true);
+            case 'retrieveAllUsers':
+                startHTML();
+                retrieveAllUsers($gapps, true);
+                endHTML(true);
+            case 'retrieveNickname':
+                startHTML();
+                retrieveNickname($gapps, true, $_REQUEST['nickname']);
+                endHTML(true);
+            case 'retrieveNicknames':
+                startHTML();
+                retrieveNicknames($gapps, true, $_REQUEST['user']);
+                endHTML(true);
+            case 'retrieveAllNicknames':
+                startHTML();
+                retrieveAllNicknames($gapps, true);
+                endHTML(true);
+            case 'retrieveEmailLists':
+                startHTML();
+                retrieveEmailLists($gapps, true, $_REQUEST['recipient']);
+                endHTML(true);
+            case 'retrieveAllEmailLists':
+                startHTML();
+                retrieveAllEmailLists($gapps, true);
+                endHTML(true);
+            case 'retrieveAllRecipients':
+                startHTML();
+                retrieveAllRecipients($gapps, true, $_REQUEST['emailList']);
+                endHTML(true);
+        }
+    }
+    
+    // Now we handle the potentially destructive commands, which have to 
+    // be submitted by POST only.
+    if (!empty($_POST['command'])) {
+        switch ($_POST['command']) {
+            case 'createUser':
+                startHTML();
+                createUser($gapps, true, $_POST['user'], 
+                    $_POST['givenName'], $_POST['familyName'], 
+                    $_POST['pass']);
+                endHTML(true);
+            case 'updateUserName':
+                startHTML();
+                updateUserName($gapps, true, $_POST['user'], 
+                    $_POST['givenName'], $_POST['familyName']);
+                endHTML(true);
+            case 'updateUserPassword':
+                startHTML();
+                updateUserPassword($gapps, true, $_POST['user'], 
+                    $_POST['pass']);
+                endHTML(true);
+            case 'setUserSuspended':
+                if ($_POST['mode'] == 'suspend') {
+                    startHTML();
+                    suspendUser($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } elseif ($_POST['mode'] == 'restore') {
+                    startHTML();
+                    restoreUser($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } else {
+                    header('HTTP/1.1 400 Bad Request');
+                    startHTML();
+                    echo "<h2>Invalid mode.</h2>\n";
+                    echo "<p>Please check your request and try again.</p>";
+                    endHTML(true);
+                }
+            case 'setUserAdmin':
+                if ($_POST['mode'] == 'issue') {
+                    startHTML();
+                    giveUserAdminRights($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } elseif ($_POST['mode'] == 'revoke') {
+                    startHTML();
+                    revokeUserAdminRights($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } else {
+                    header('HTTP/1.1 400 Bad Request');
+                    startHTML();
+                    echo "<h2>Invalid mode.</h2>\n";
+                    echo "<p>Please check your request and try again.</p>";
+                    endHTML(true);
+                }
+            case 'setForceChangePassword':
+                if ($_POST['mode'] == 'set') {
+                    startHTML();
+                    setUserMustChangePassword($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } elseif ($_POST['mode'] == 'clear') {
+                    startHTML();
+                    clearUserMustChangePassword($gapps, true, $_POST['user']);
+                    endHTML(true);
+                } else {
+                    header('HTTP/1.1 400 Bad Request');
+                    startHTML();
+                    echo "<h2>Invalid mode.</h2>\n";
+                    echo "<p>Please check your request and try again.</p>";
+                    endHTML(true);
+                }
+            case 'deleteUser':
+                startHTML();
+                deleteUser($gapps, true, $_POST['user']);
+                endHTML(true);
+            case 'createNickname':
+                startHTML();
+                createNickname($gapps, true, $_POST['user'], 
+                    $_POST['nickname']);
+                endHTML(true);
+            case 'deleteNickname':
+                startHTML();
+                deleteNickname($gapps, true, $_POST['nickname']);
+                endHTML(true);
+            case 'createEmailList':
+                startHTML();
+                createEmailList($gapps, true, $_POST['emailList']);
+                endHTML(true);
+            case 'deleteEmailList':
+                startHTML();
+                deleteEmailList($gapps, true, $_POST['emailList']);
+                endHTML(true);
+            case 'modifySubscription':
+                if ($_POST['mode'] == 'subscribe') {
+                    startHTML();
+                    addRecipientToEmailList($gapps, true, $_POST['recipient'], 
+                        $_POST['emailList']);
+                    endHTML(true);
+                } elseif ($_POST['mode'] == 'unsubscribe') {
+                    startHTML();
+                    removeRecipientFromEmailList($gapps, true, 
+                        $_POST['recipient'], $_POST['emailList']);
+                    endHTML(true);
+                } else {
+                    header('HTTP/1.1 400 Bad Request');
+                    startHTML();
+                    echo "<h2>Invalid mode.</h2>\n";
+                    echo "<p>Please check your request and try again.</p>";
+                    endHTML(true);
+                }
+        }
+    }
+    
+    // Check for an invalid command. If so, display an error and exit.
+    if (!empty($_REQUEST['command'])) {
+        header('HTTP/1.1 400 Bad Request');
+        startHTML();
+        echo "<h2>Invalid command.</h2>\n";
+        echo "<p>Please check your request and try again.</p>";
+        endHTML(true);
+    }
+    
+    // If a menu parameter is available, display a submenu.
+    if (!empty($_REQUEST['menu'])) {
+        switch ($_REQUEST['menu']) {
+            case 'user':
+                startHTML();
+                displayUserMenu();
+                endHTML();
+            case 'nickname':
+                startHTML();
+                displayNicknameMenu();
+                endHTML();
+            case 'emailList':
+                startHTML();
+                displayEmailListMenu();
+                endHTML();
+            case 'logout':
+                startHTML(false);
+                logout();
+                endHTML();
+            default:
+                header('HTTP/1.1 400 Bad Request');
+                startHTML();
+                echo "<h2>Invalid menu selection.</h2>\n";
+                echo "<p>Please check your request and try again.</p>";
+                endHTML(true);
+        }
+    }
+    
+    // If we get this far, that means there's nothing to do. Display 
+    // the main menu.
+    // If no command was issued and no menu was selected, display the 
+    // main menu.
+    startHTML();
+    displayMenu();
+    endHTML();
+}
+
+// ************************** PROGRAM ENTRY POINT **************************
+
+if (!isset($_SERVER["HTTP_HOST"]))  {
+    // running through command line
+    runCLIVersion($argv, $argc);
+} else {
+    // running through web server
+    try {
+        runWWWVersion();
+    } catch (Zend_Gdata_Gapps_ServiceException $e) {
+        // Try to recover gracefully from a service exception.
+        // The HTML prologue will have already been sent.
+        echo "<p><strong>Service Error Encountered</strong></p>\n";
+        echo "<pre>" . htmlspecialchars($e->__toString()) . "</pre>";
+        endHTML(true);
+    }
+}

+ 670 - 0
demos/Zend/Gdata/Gbase.php

@@ -0,0 +1,670 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/* Load the Zend Gdata classes. */
+require_once 'Zend/Loader.php';
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+Zend_Loader::loadClass('Zend_Gdata_Gbase');
+
+/* The items feed URL, used for queries, insertions and batch commands. */
+define('ITEMS_FEED_URI', 'http://www.google.com/base/feeds/items');
+
+/* Types of cuisine the user may select when inserting a recipe. */
+$cuisines = array('African', 'American', 'Asian', 'Caribbean', 'Chinese',
+  'French', 'Greek', 'Indian', 'Italian', 'Japanese', 'Jewish', 
+  'Mediterranean', 'Mexican', 'Middle Eastern', 'Moroccan', 
+  'North American', 'Spanish', 'Thai', 'Vietnamese', 'Other');
+
+
+/**
+ * Inserts a new recipe by performing an HTTP POST to the
+ * items feed.
+ * @param boolean $dryRun (optional) True if this should be a dry run insert
+ * @return Zend_Gdata_Gbase_ItemFeed The newly created entry 
+ */
+function postItem($dryRun = false) { 
+  $client = Zend_Gdata_AuthSub::getHttpClient($_POST['token']);
+  $gdata = new Zend_Gdata_Gbase($client);
+
+  $newEntry = $gdata->newItemEntry();
+  
+  // Add title
+  $newEntry->title = $gdata->newTitle(trim($_POST['recipe_title']));
+
+  // Add some content
+  $newEntry->content = $gdata->newContent($_POST['recipe_text']);
+  $newEntry->content->type = 'text';
+
+  // Define item type
+  $newEntry->itemType = 'testrecipes';
+  $newEntry->itemType->type = 'text';
+
+  // Add item-specific attributes
+  $newEntry->addGbaseAttribute('cuisine', $_POST['cuisine'], 'text');
+  $newEntry->addGbaseAttribute('cooking_time', $_POST['time_val'] . ' ' . 
+      $_POST['time_units'], 'intUnit');
+  $newEntry->addGbaseAttribute('main_ingredient', 
+                               $_POST['main_ingredient'], 
+                              'text');
+  $newEntry->addGbaseAttribute('serving_count', $_POST['serves'], 'number');
+
+  // Post the item
+  $createdEntry = $gdata->insertGbaseItem($newEntry, $dryRun);
+
+  return $createdEntry;
+}
+
+/**
+ * Updates an existing recipe by performing an HTTP PUT
+ * on its feed URI, using the updated values as the data.
+ * @return true
+ */
+function updateItem() { 
+  $client = Zend_Gdata_AuthSub::getHttpClient($_POST['token']);
+  $gdata = new Zend_Gdata_Gbase($client);
+  
+  $itemUrl = $_POST['link'];
+  $updatedEntry = $gdata->getGbaseItemEntry($itemUrl);
+  
+  // Update title
+  $updatedEntry->title = $gdata->newTitle(trim($_POST['recipe_title']));
+
+  // Update content
+  $updatedEntry->content = $gdata->newContent($_POST['recipe_text']);
+  $updatedEntry->content->type = 'text';
+
+  // Update item-specific attributes
+  $baseAttributeArr = $updatedEntry->getGbaseAttribute('cuisine');
+  if (is_object($baseAttributeArr[0])) {
+    $baseAttributeArr[0]->text = $_POST['cuisine'];
+  }
+  
+  $baseAttributeArr = $updatedEntry->getGbaseAttribute('cooking_time');
+  if (is_object($baseAttributeArr[0])) {
+    $baseAttributeArr[0]->text = 
+        $_POST['time_val'] . ' ' . $_POST['time_units'];
+  }
+  
+  $baseAttributeArr = $updatedEntry->getGbaseAttribute('main_ingredient');
+  if (is_object($baseAttributeArr[0])) {
+    $baseAttributeArr[0]->text = $_POST['main_ingredient'];
+  }
+  
+  $baseAttributeArr = $updatedEntry->getGbaseAttribute('serving_count');
+  if (is_object($baseAttributeArr[0])) {
+    $baseAttributeArr[0]->text = $_POST['serves'];
+  }
+
+  $dryRun = false;
+  $gdata->updateGbaseItem($updatedEntry, $dryRun); 
+  
+  // Alternatively, you can call the save() method directly on the entry
+  // $updatedEntry->save();
+  
+  return true;
+}
+
+/**
+ * Deletes a recipe by performing an HTTP DELETE on its feed URI.
+ * @return void 
+ */
+function deleteItem() {
+  $client = Zend_Gdata_AuthSub::getHttpClient($_POST['token']);
+  $gdata = new Zend_Gdata_Gbase($client);
+
+  $itemUrl = $_POST['link'];
+  $deleteEntry = $gdata->getGbaseItemEntry($itemUrl);
+  
+  $dryRun = false;
+  $gdata->deleteGbaseItem($deleteEntry, $dryRun);
+  
+  // Alternatively, you can call the save() method directly on the entry
+  // $gdata->delete($itemUrl);
+}
+
+/**
+ * Creates the XML content used to perform a batch delete.
+ * @return string The constructed XML to be used for the batch delete 
+ */
+function buildBatchXML() {
+  $result =  '<?xml version="1.0" encoding="UTF-8"?>' . "\n" . 
+             '<feed xmlns="http://www.w3.org/2005/Atom"' . "\n" . 
+             ' xmlns:g="http://base.google.com/ns/1.0"' . "\n" . 
+             ' xmlns:batch="http://schemas.google.com/gdata/batch">' . "\n";
+  
+  $counter = 0;
+  foreach($_POST as $key => $value) {
+    if(substr($key, 0, 5) == "link_") {
+      $counter++;
+
+      $result .= '<entry>' . "\n" . 
+                 '<id>' . $value . '</id>' . "\n" . 
+                 '<batch:operation type="delete"/>' . "\n" . 
+                 '<batch:id>' . $counter . '</batch:id>' . "\n" . 
+                 '</entry>' . "\n";
+    }
+  }
+  $result .= '</feed>' . "\n";
+
+  return $result;
+}
+
+/**
+ * Deletes all recipes by performing an HTTP POST to the
+ * batch URI.
+ * @return Zend_Http_Response The reponse of the post 
+ */
+function batchDelete() {
+  $client = Zend_Gdata_AuthSub::getHttpClient($_POST['token']);
+  $gdata = new Zend_Gdata_Gbase($client);
+  
+  $response = $gdata->post(buildBatchXML(), ITEMS_FEED_URI . '/batch');
+  
+  return $response;
+}
+
+/**
+ * Writes the HTML header for the demo.
+ *
+ * NOTE: We would normally keep the HTML/CSS markup separate from the business
+ *       logic above, but have decided to include it here for simplicity of
+ *       having a single-file sample.
+ * @return void
+ */
+function printHTMLHeader()
+{
+  print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"' . "\n" . 
+        '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n" . 
+        '<html xmlns="http://www.w3.org/1999/xhtml" lang="en">' . "\n" . 
+        '<head><meta http-equiv="Content-Type" ' . 
+        'content="text/html;charset=utf-8"/>' . "\n" . 
+        '<title>PHP Demo: Google Base API</title>' . "\n" . 
+        '<link rel="stylesheet" type="text/css" ' . 
+        'href="http://code.google.com/css/dev_docs.css">' . "\n" . 
+        '</head>' . "\n" . 
+        '<body><center>' . "\n";
+}
+
+/**
+ * Writes the HTML footer for the demo.
+ *
+ * NOTE: We would normally keep the HTML/CSS markup separate from the business
+ *       logic above, but have decided to include it here for simplicity of
+ *       having a single-file sample.
+ * @return void 
+ */
+function printHTMLFooter() {
+  print '</center></body></html>' . "\n";
+}
+
+/**
+ * We arrive here when the user first comes to the form. The first step is
+ * to have them get a single-use token.
+ */
+function showIntroPage() {
+  $next_url = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
+  $scope = ITEMS_FEED_URI;
+  $secure = false;
+  $session = true;
+  $redirect_url = Zend_Gdata_AuthSub::getAuthSubTokenUri($next_url, 
+                                                         $scope, 
+                                                         $secure, 
+                                                         $session);
+
+  printHTMLHeader();
+  
+  print '<table style="width:50%;">' . "\n" . 
+        '<tr>' . "\n" .  
+        '<th colspan="2" style="text-align:center;">' . 
+        'PHP Demo: Google Base data API<br/>' . 
+        '<small><span style="font-variant: small-caps;">Powered By</span>' . 
+        ' <a href="http://framework.zend.com/download/gdata">' . 
+        'Zend Google Data Client Library</a></small></th>' . "\n" . 
+        '</tr>' . "\n" . 
+        '<tr><td>Before you get started, please <a href="' . $redirect_url . 
+        '">sign in</a> to your personal Google Base account.</td></tr>' . "\n" . 
+        '</table>' . "\n";
+  
+  printHTMLFooter();
+}
+
+/**
+ * Prints the table of recipes the user has already entered
+ * on the left-hand side of the page.
+ * @param string $token The session token
+ * @return void 
+ */
+function showRecipeListPane($token) {
+  $client = Zend_Gdata_AuthSub::getHttpClient($token);
+  $gdata = new Zend_Gdata_Gbase($client);
+  try {
+    $feed = $gdata->getGbaseItemFeed(ITEMS_FEED_URI . '/-/testrecipes');
+   
+    print '<td style="width:50%;text-align:center;vertical-align:top">' . "\n" .
+          '<a href="http://www.google.com/base/dashboard" target="_blank">' . 
+          'View all of your published items</a>' . 
+          '<table>' . "\n" . 
+          '<tr><th colspan="5" style="text-align:center">' . 
+          'Recipes you have added that searchable via the API</th></tr>' . "\n";
+    
+    if ($feed->count() == 0) {
+      print '<tr style="font-style:italic">' . 
+            '<td colspan="5" style="text-align:center">(none)</td>' . 
+            '</tr>' . "\n";
+    } else {
+      print '<tr style="font-style:italic">' . "\n" . 
+            '<td style="text-align:center">Name</td>' . "\n" . 
+            '<td style="text-align:center">Cuisine</td>' . "\n" . 
+            '<td style="text-align:center">Serves</td>' . "\n" . 
+            '<td colspan="2" style="text-align:center">Actions</td>' . "\n" . 
+            '</tr>' . "\n";
+
+      foreach ($feed->entries as $feed_entry) {    
+        $href = $feed_entry->link[0]->href;
+        $title = $feed_entry->title->text;
+        $id = $feed_entry->id->text;
+        
+        $baseAttributeArr = $feed_entry->getGbaseAttribute('cuisine');
+        // Only want first cuisine
+        if (isset($baseAttributeArr[0]) && is_object($baseAttributeArr[0])) {
+          $cuisine = $baseAttributeArr[0]->text;
+        } 
+        
+        $baseAttributeArr = $feed_entry->getGbaseAttribute('serving_count');
+        // Only want first serving_count
+        if (isset($baseAttributeArr[0]) && is_object($baseAttributeArr[0])) {
+          $serving_count = $baseAttributeArr[0]->text;
+        } 
+
+        print '<tr>' . "\n" .      
+              '<td align="left" valign="top"><b><a href="' . $href . '">' .
+              $title . '</a></b></td>' . "\n" .
+              '<td style="text-align:center;vertical-align:top">' .
+              $cuisine . '</td>' . "\n" .
+              '<td style="text-align:center;vertical-align:top">' .
+              $serving_count . '</td>' . "\n";
+
+        /* Create an Edit button for each existing recipe. */
+        print '<td style="text-align:center;vertical-align:top">' . "\n" .
+              '<form method="post" action="' . $_SERVER['PHP_SELF'] .
+              '" style="margin-top:0;margin-bottom:0;">' . "\n" .
+              '<input type="hidden" name="action" value="edit">' . "\n" .
+              "<input type=\"hidden\" name=\"token\" value=\"$token\">" . "\n" .
+              '<input type="hidden" name="edit" value="' . $id . '">' . "\n" .
+              '<input type="submit" value="Edit">' . "\n" .
+              '</form>' . "\n" .
+              '</td>' . "\n";
+
+        /* Create a Delete button for each existing recipe. */
+        print '<td style="text-align:center; vertical-align:top">' . "\n" .
+              '<form method="post" action="' . $_SERVER['PHP_SELF'] .
+              '" style="margin-top:0;margin-bottom:0;">' . "\n" .
+              '<input type="hidden" name="action" value="delete">' . "\n" .
+              "<input type=\"hidden\" name=\"token\" value=\"$token\">" . "\n" .
+              '<input type="hidden" name="link" value="' . $id . '">' . "\n" .
+              '<input type="submit" value="Delete">' . "\n" .
+              '</form>' . "\n" .
+              '</td>' . "\n" .
+              '</tr>' . "\n";
+      }
+    }
+
+    /* Create a "Delete all" button" to demonstrate batch requests. */
+    print '<tr><td colspan="5" style="text-align:center">' . "\n" .
+          '<form method="post" action="' . $_SERVER['PHP_SELF'] .
+          '" style="margin-top:0;margin-bottom:0">' . "\n" .
+          '<input type="hidden" name="action" value="delete_all">' . "\n" .
+          '<input type="hidden" name="token" value="' . $token . '">' . "\n";
+
+    $i = 0;
+    foreach ($feed as $feed_entry) {
+      print '<input type="hidden" name="link_' . $i . '" value="' .
+            $feed_entry->id->text . '">' . "\n";
+      $i++;
+    }
+
+    print '<input type="submit" value="Delete All"';
+    if ($feed->count() == 0) {
+      print ' disabled="true"';
+    }
+    print '></form></td></tr>' . "\n";
+    print '</table>' . "\n";
+    
+    print '</td>' . "\n";
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu("Error: " . $e->getMessage(), $token);
+  }
+}
+
+/**
+ * Prints a small form allowing the user to insert a new
+ * recipe.
+ * @param string $sessionToken A session token
+ * @return void 
+ */
+function showRecipeInsertPane($sessionToken) {
+  global $cuisines;
+
+  print '<td valign="top" width="50%">' . "\n" .
+        '<table width="90%">' . "\n" .
+        '<tr><th colspan="2" style="text-align:center">' . 
+        'Insert a new recipe</th></tr>' . "\n" .
+        '<form method="post" action="' . $_SERVER['PHP_SELF'] . '">' . "\n" .
+        '<input type="hidden" name="action" value="insert">' . "\n" .
+        "<input type=\"hidden\" name=\"token\" value=\"$sessionToken\">\n" .
+        '<tr><td align="right">Title:</td>' . "\n" .
+        '<td><input type="text" name="recipe_title" class="half">' .
+        '</td></tr>' . "\n" .
+        '<tr><td align="right">Main ingredient:</td>' . "\n" .
+        '<td><input type="text" name="main_ingredient" class="half">' .
+        '</td></tr>' . "\n" .
+        '<tr><td align="right">Cuisine:</td>' . "\n" .
+        '<td><select name="cuisine" class="half">' . "\n";
+
+  foreach ($cuisines as $curCuisine) {
+    print "<option value=\"$curCuisine\">$curCuisine</option>\n";
+  }
+
+  print '</select></td></tr>' . "\n" .
+        '<tr><td align="right">Cooking Time:</td>' .
+        '<td><input type="text" name="time_val" size=2 maxlength=2>&nbsp;' .
+        '<select name="time_units"><option value="minutes">minutes</option>' .
+        '<option value="hours">hours</option></select></td></tr>' . "\n" .
+        '<tr><td align="right">Serves:</td>' . "\n" .
+        '<td><input type="text" name="serves" size=2 maxlength=3></td>' . 
+        '</tr>' . "\n" .
+        '<tr><td align="right">Recipe:</td>' . "\n" .
+        '<td><textarea class="full" name="recipe_text"></textarea></td>' . 
+        '</tr>' . "\n" .
+        '<td>&nbsp;</td><td><input type="submit" value="Submit"></td>' . "\n" .
+        '</form></tr></table>' . "\n" .
+        '</td>' . "\n";
+}
+
+/**
+ * Shows a menu allowing the user to update an existing
+ * recipe with the Base API update feature.
+ * @return void  
+ */
+function showEditMenu() {
+  global $cuisines;
+
+  $client = Zend_Gdata_AuthSub::getHttpClient($_POST['token']);
+  $gdata = new Zend_Gdata_Gbase($client);
+
+  try {
+    $feed = $gdata->getGbaseItemFeed(ITEMS_FEED_URI);
+    foreach ($feed->entries as $feed_entry) {
+      $editLink = $feed_entry->link[2]->href;
+      
+      if ($editLink == $_POST['edit']) {
+        $baseAttributeArr = $feed_entry->getGbaseAttribute('cooking_time');
+        if (isset($baseAttributeArr[0]) && is_object($baseAttributeArr[0])) {
+          $splitCookingTime = split(' ', $baseAttributeArr[0]->text);
+        }
+        
+        $baseAttributeArr = $feed_entry->getGbaseAttribute('cuisine');
+        // Cuisine can have multiple entries
+        if (isset($baseAttributeArr[0]) && is_object($baseAttributeArr[0])) {
+          $cuisine = $baseAttributeArr[0]->text;
+        }
+        
+        $baseAttributeArr = $feed_entry->getGbaseAttribute('serving_count');
+        // $serving_count can have multiple entries
+        if (isset($baseAttributeArr[0]) && is_object($baseAttributeArr[0])) {
+          $serving_count = $baseAttributeArr[0]->text;
+        } 
+        
+        $main_ingredient = $feed_entry->getGbaseAttribute('main_ingredient');
+        // Main_ingredient can have multiple entries
+        if (is_array($main_ingredient)) {
+          $main_ingredient = $main_ingredient[0]->text;
+        } 
+        
+        printHTMLHeader();
+
+        print '<table style="width:50%">' . "\n";
+        print '<tr>' . 
+              '<th colspan="2" style="text-align:center">Edit recipe:</th>' . 
+              '</tr>' . "\n";
+
+        print "<form method=\"post\" action=\"{$_SERVER['PHP_SELF']}\">\n" .
+              '<input type="hidden" name="action" value="update">' . "\n" . 
+              '<input type="hidden" name="link" value="' .
+              $_POST['edit'] . '">' . "\n" .
+              '<input type="hidden" name="token" value="' .
+              $_POST['token'] . '">' . "\n";
+
+        print '<tr><td align="right">Title:</td>' . "\n" . 
+              '<td>' . 
+              '<input type="text" name="recipe_title" class="half" value="' .
+              $feed_entry->title->text . '">' . 
+              '</td></tr>' . "\n";
+
+        print '<tr><td align="right">Main ingredient:</td>' . "\n" .
+              '<td><input type="text" name="main_ingredient" value="' .
+               $main_ingredient . '" class="half"></td></tr>' . "\n";
+
+        print '<tr><td align="right">Cuisine:</td>' . "\n" . 
+              '<td><select name="cuisine" class="half">' . "\n";
+
+        foreach ($cuisines as $curCuisine) {
+          print '<option value="' . $curCuisine . '"';
+          if ($curCuisine == $cuisine) {
+            print ' selected="selected"';
+          }
+          print '>' . $curCuisine . "</option>\n";
+        }
+
+        print '</select></td></tr>' . "\n";
+        print '<tr><td align="right">Cooking Time:</td>' .
+              '<td><input type="text" name="time_val" size="2" maxlength="2" ' . 
+              'value="' . $splitCookingTime[0] . '">&nbsp;' . "\n" . 
+              '<select name="time_units">' . "\n";
+        if ($splitCookingTime[1] == "minutes") {
+          print '<option value="minutes" selected="selected">minutes</option>' .
+            "\n";
+          print '<option value="hours">hours</option>' . "\n";
+        } else {
+          print '<option value="minutes">minutes</option>' . "\n";
+          print '<option value="hours" selected="selected">hours</option>' .
+            "\n";
+        }
+
+        print '</select></td></tr>' . "\n" . 
+              '<tr><td align="right">Serves:</td>' . "\n" . 
+              '<td><input type="text" name="serves" value="' .
+              $serving_count . '" size="2" maxlength="3"></td></tr>' . "\n" . 
+              '<tr><td align="right">Recipe:</td>' . "\n" . 
+              '<td><textarea class="full" name="recipe_text">' .
+              $feed_entry->content->text . '</textarea></td></tr>' . "\n" . 
+              '<td>&nbsp;</td><td><input type="submit" value="Update">' . 
+              '</td>' . "\n" . 
+              '</form></tr></table>' . "\n";
+        
+        printHTMLFooter();
+
+        break;
+      }
+    }
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu($e->getMessage(), $_POST['token']);
+  }
+}
+
+/**
+ * Displays both the "List of current recipes" and
+ * "Insert a new recipe" panels in a single table.
+ * @param string $tableTitle The title to display in the html table
+ * @param string $sessionToken A session token 
+ * @return void  
+ */
+function showMainMenu($tableTitle, $sessionToken) {
+  printHTMLHeader();
+  
+  print '<table style="width: 75%;text-align:center">' . "\n" . 
+        '<tr>' . "\n" . 
+        '<th colspan="2" style="text-align:center;">' . 
+        'PHP Demo: Google Base data API<br />' . 
+        '<font size="-1">' . 
+        '<span style="font-variant: small-caps;">Powered By</span> ' .
+        '<a href="http://framework.zend.com/download/gdata">' .
+        'Zend Google Data Client Library</a></font></th>' . "\n" . 
+        '</tr>' . "\n" . 
+        '<tr><td colspan="2" align="center">' . $tableTitle . "</td></tr>\n" . 
+        '<tr>' . "\n";
+
+  // Create the two sub-tables.
+  showRecipeListPane($sessionToken);
+  showRecipeInsertPane($sessionToken);
+
+  // Add a "Sign out" link.
+  print '<tr><th colspan="2" style="text-align: center">Or click here to' .
+        ' <a href="http://www.google.com/accounts/Logout">sign out</a>' .
+        ' of your Google account.</th></tr>' . "\n";
+
+  // Close the master table.
+  print '</table>' . "\n";
+  
+  printHTMLFooter();
+}
+
+/**
+ * Exchanges the given single-use token for a session
+ * token using AuthSubSessionToken, and returns the result.
+ * @param string $token The single-use token from AuthSubRequest
+ * @return string The upgraded (session) token
+ */
+function exchangeToken($token) {
+  return Zend_Gdata_AuthSub::getAuthSubSessionToken($token);
+}
+
+/**
+ * We arrive here after the user first authenticates and we get back
+ * a single-use token.
+ * @return void 
+ */
+function showFirstAuthScreen() {
+  $singleUseToken = $_GET['token'];
+  $sessionToken = exchangeToken($singleUseToken);
+
+  if (!$sessionToken) {
+    showIntroPage();
+  } else {
+    $tableTitle = "Here's your <b>single use token:</b> " . 
+      "<code>$singleUseToken</code><br/>" . "\n" . 
+      "And here's the <b>session token:</b> <code>$sessionToken</code>";
+    
+      showMainMenu($tableTitle, $sessionToken);
+  }
+}
+
+/**
+ * Main logic to handle the POST operation of inserting an item.
+ * @return void 
+ */
+function handlePost() {
+  try {
+    $newEntry= postItem();
+    if ($newEntry) {
+      showMainMenu('Recipe inserted!  It will be searchable by the API soon...',
+                    $_POST['token']);
+    } 
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu('Recipe insertion failed: ' . $e->getMessage(),
+                 $_POST['token']);
+  }
+}
+
+/**
+ * Main logic to handle deleting an item.
+ * @return void 
+ */
+function handleDelete() {
+  try {
+    deleteItem();
+    showMainMenu('Recipe deleted.', $_POST['token']);
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu('Recipe deletion failed: ' . $e->getMessage(), 
+                 $_POST['token']);
+  }
+}
+
+/**
+ * Main logic to handle a batch deletion of items.
+ * @return void 
+ */
+function handleBatch() {
+  try {
+    $batch_response = batchDelete();
+    if ($batch_response->isSuccessful()) {
+      showMainMenu('All recipes deleted.', $_POST['token']);
+    } else {
+      showMainMenu('Batch deletion failed: ' . $batch_response->getMessage(),
+                   $_POST['token']);
+    }
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu('Batch deletion failed: ' . $e->getMessage(), $_POST['token']);
+  }
+}
+
+/**
+ * Main logic to handle updating an item
+ * @return void 
+ */
+function handleUpdate() {
+  try {
+    if (updateItem()) {
+      showMainMenu('Recipe successfully updated.', $_POST['token']);
+    } else {
+      showMainMenu('Recipe update failed.', $_POST['token']);
+    }
+  } catch (Zend_Gdata_App_Exception $e) {
+    showMainMenu('Recipe update failed: ' . $e->getMessage(), $_POST['token']);
+  }
+}
+
+/**
+ * Main logic to handle requests
+ */
+if (count($_GET) == 1 && array_key_exists('token', $_GET)) {
+  showFirstAuthScreen();
+} else {
+  if (count($_POST) == 0) {
+    showIntroPage();
+  } else {
+    if ($_POST['action'] == 'insert') {
+      handlePost();
+    } else if ($_POST['action'] == 'delete') {
+      handleDelete();
+    } else if ($_POST['action'] == 'delete_all') {
+      handleBatch();
+    } else if ($_POST['action'] == 'edit') {
+      showEditMenu();
+    } else if ($_POST['action'] == 'update') {
+      handleUpdate();
+    } else {
+      showIntroPage();
+    }
+  }
+}
+
+?>

+ 386 - 0
demos/Zend/Gdata/Health.php

@@ -0,0 +1,386 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+//////////////////////////////////////////////////////////////////////////////
+// Configuration: You must change these settings before running this sample //
+//////////////////////////////////////////////////////////////////////////////
+
+// Change this to point to the location of your private signing key. See:
+// http://code.google.com/apis/health/getting_started.html#DomainRegistration
+define('HEALTH_PRIVATE_KEY', '/path/to/your/rsa_private_key.pem');
+
+//////////////////////////////////////////////////////////////////////////////
+// End Configuration                                                        //
+//////////////////////////////////////////////////////////////////////////////
+
+// Load the Zend Gdata classes.
+require_once 'Zend/Loader.php';
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+Zend_Loader::loadClass('Zend_Gdata_Health');
+Zend_Loader::loadClass('Zend_Gdata_Health_Query');
+
+session_start();
+
+// Google H9 Sandbox AuthSub/OAuth scope
+define('SCOPE', 'https://www.google.com/h9/feeds/');
+
+try {
+  // Setup the HTTP client and fetch an AuthSub token for H9
+  $client = authenticate(@$_GET['token']);
+  $useH9 = true;
+  $healthService = new Zend_Gdata_Health($client, 'google-HealthPHPSample-v1.0', $useH9);
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+?>
+
+<html>
+<head>
+<style>
+body { margin:0; }
+div { width:75%;margin-left:10px;padding:5px;font-family: "Courier New"; }
+div#tokenstats { border-bottom:5px solid;background-color:#99ccff;margin:0 0 10px 0;padding:5px;width:100%; }
+.code { margin:5px 0 5px 10px;background-color:#eee; }
+div.data{ height:600px;border:1px solid;overflow:auto; }
+</style>
+</head>
+<body>
+
+<div id="tokenstats">
+  <b>Token info</b>: <?php echo getTokenInfo($client); ?><br>
+  <b>Session Token</b>: <?php echo $client->getAuthSubToken(); ?><br>
+</div>
+
+<?php
+// =============================================================================
+// Return the user's entire profile in a single atom <entry>
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Return the user\'s entire profile in a single atom <entry>
+    // =========================================================================
+
+    $query = new Zend_Gdata_Health_Query();
+    $query->setDigest("true");
+    $profileFeed = $healthService->getHealthProfileFeed($query);';
+
+  $snippet2 = '
+    $entries = $profileFeed->getEntries();
+    $ccr = $entries[0]->getCcr();
+    $xmlStr = $ccr->saveXML($ccr);
+    echo "<p>" . xmlpp($xmlStr) . "</p>";';
+  echo '<div class="code"><pre>' . htmlentities($snippet . $snippet2) . '</pre></div>';
+  eval($snippet);
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="data"><pre>';
+echo 'num entries: ' . count($profileFeed->getEntries());
+eval($snippet2);
+echo '</pre></div>';
+
+// =============================================================================
+// Return a user's medication from the entire CCR
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Return a user\'s medication for the entire CCR
+    // =========================================================================
+    $profileFeed = $healthService->getHealthProfileFeed();';
+
+  $snippet2 = '
+    foreach ($profileFeed->entry as $entry) {
+      $medications = $entry->getCcr()->getMedications();
+      foreach ($medications as $med) {
+        $xmlStr = $med->ownerDocument->saveXML($med);
+        echo "<p>" . xmlpp($xmlStr) . "</p>";
+      }
+    }';
+
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="code"><pre>' . htmlentities($snippet . $snippet2) . '</pre></div>';
+eval($snippet);
+
+echo '<div class="data"><pre>';
+eval($snippet2);
+echo '</pre></div>';
+
+// =============================================================================
+// Category query: return a user's medication
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Category query: return a user\'s medication
+    // =========================================================================
+
+    $query = new Zend_Gdata_Health_Query(SCOPE . "profile/default");
+    $query->setCategory("medication");
+    $profileFeed = $healthService->getHealthProfileFeed($query);';
+
+  $snippet2 = '
+    foreach ($profileFeed->entry as $entry) {
+      $medications = $entry->getCcr()->getMedications();
+      foreach ($medications as $med) {
+        $xmlStr = $med->ownerDocument->saveXML($med);
+        echo "<p>" . xmlpp($xmlStr) . "</p>";
+      }
+    }';
+
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="code"><pre>' . htmlentities($snippet . $snippet2) . '</pre></div>';
+eval($snippet);
+
+echo '<div class="data"><pre>';
+echo 'query: ' . $query->getQueryUrl() . '<br>';
+echo 'num entries: ' . count($profileFeed->getEntries());
+eval($snippet2);
+echo '</pre></div>';
+
+// =============================================================================
+// Query for a specific item within a category: allgergy A-Fil
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Query for a specific item within a category: allgergy A-Fil
+    // =========================================================================
+
+    $query = new Zend_Gdata_Health_Query(SCOPE . "profile/default");
+    $query->setCategory("allergy", "A-Fil");
+    $query->setGrouped("true");
+    $profileFeed = $healthService->getHealthProfileFeed($query);';
+
+  $snippet2 = '
+    foreach ($profileFeed->getEntries() as $entry) {
+      $allergies = $entry->getCcr()->getAllergies();
+      foreach ($allergies as $allergy) {
+        $xmlStr = $allergy->ownerDocument->saveXML($allergy);
+        echo "<p>" . xmlpp($xmlStr) . "</p>";
+      }
+    }';
+
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="code"><pre>' . htmlentities($snippet . $snippet2) . '</pre></div>';
+eval($snippet);
+
+echo '<div class="data"><pre>';
+echo 'query: ' . $query->getQueryUrl() . '<br>';
+echo 'num entries: ' . count($profileFeed->getEntries());
+eval($snippet2);
+echo '</pre></div>';
+
+// =============================================================================
+// Query (and return) the user\'s medications OR conditions
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Query (and return) the user\'s medications OR conditions
+    // =========================================================================
+
+    $queryStr = SCOPE . "profile/default/-/medication%7Ccondition?digest=false";
+    $profileFeed = $healthService->getHealthProfileFeed($queryStr);';
+  $snippet2 = '
+    $entries = $profileFeed->getEntries();
+    foreach ($entries as $entry) {
+      $ccr = $entry->getCcr();
+      $xmlStr = $ccr->saveXML($ccr);
+      echo "<p>" . xmlpp($xmlStr) . "</p>";
+    }';
+
+  echo '<div class="code"><pre>' . htmlentities($snippet . $snippet2) . '</pre></div>';
+  eval($snippet);
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="data"><pre>';
+echo 'num entries: ' . count($profileFeed->getEntries());
+eval($snippet2);
+echo '</pre></div>';
+
+// =============================================================================
+// Send a notice to the user's profile that includes a CCR payload
+// =============================================================================
+try {
+  $snippet = '
+    // =========================================================================
+    // Send a notice to the user\'s profile that includes a CCR payload
+    // =========================================================================
+
+    $subject = "Title of your notice goes here";
+    $body = "Notice body can contain <b>html</b> entities";
+    $ccr = \'<ContinuityOfCareRecord xmlns="urn:astm-org:CCR">
+      <Body>
+       <Problems>
+        <Problem>
+          <DateTime>
+            <Type><Text>Start date</Text></Type>
+            <ExactDateTime>2007-04-04T07:00:00Z</ExactDateTime>
+          </DateTime>
+          <Description>
+            <Text>Aortic valve disorders</Text>
+            <Code>
+              <Value>410.10</Value>
+              <CodingSystem>ICD9</CodingSystem>
+              <Version>2004</Version>
+            </Code>
+          </Description>
+          <Status><Text>Active</Text></Status>
+        </Problem>
+      </Problems>
+      </Body>
+    </ContinuityOfCareRecord>\';
+    $responseEntry = $healthService->sendHealthNotice($subject, $body, "html", $ccr);';
+} catch(Zend_Gdata_App_Exception $e) {
+  echo 'Error: ' . $e->getMessage();
+}
+
+echo '<div class="code"><pre>' . htmlentities($snippet) . '</pre></div>';
+eval($snippet);
+
+echo '<div class="data"><pre>';
+echo xmlpp($responseEntry->getXML());
+echo '</pre></div>';
+
+// =============================================================================
+// Revoke the AuthSub session token
+// =============================================================================
+//$revoked = Zend_Gdata_AuthSub::AuthSubRevokeToken($client->getAuthSubToken(), $client) ? 'yes' : 'no';
+//echo '<b>Token revoked</b>: ' . @$revoked;
+//unset($_SESSION['sessionToken']);
+?>
+</body>
+</html>
+
+<?php
+function getCurrentUrl() {
+  $phpRequestUri =
+    htmlentities(substr($_SERVER['REQUEST_URI'], 0, strcspn($_SERVER['REQUEST_URI'], "\n\r")), ENT_QUOTES);
+
+  if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
+    $protocol = 'https://';
+  } else {
+    $protocol = 'http://';
+  }
+  $host = $_SERVER['HTTP_HOST'];
+  if ($_SERVER['SERVER_PORT'] != '' &&
+     (($protocol == 'http://' && $_SERVER['SERVER_PORT'] != '80') ||
+     ($protocol == 'https://' && $_SERVER['SERVER_PORT'] != '443'))) {
+    $port = ':' . $_SERVER['SERVER_PORT'];
+  } else {
+    $port = '';
+  }
+  return $protocol . $host . $port . $phpRequestUri;
+}
+
+function authenticate($singleUseToken=null) {
+  $sessionToken = isset($_SESSION['sessionToken']) ? $_SESSION['sessionToken'] : null;
+
+  // If there is no AuthSub session or one-time token waiting for us,
+  // redirect the user to Google Health's AuthSub handler to get one.
+  if (!$sessionToken && !$singleUseToken) {
+    $next = getCurrentUrl();
+    $secure = 1;
+    $session = 1;
+    $authSubHandler = 'https://www.google.com/h9/authsub';
+    $permission = 1;  // 1 - allows reading of the profile && posting notices
+    $authSubURL =
+      Zend_Gdata_AuthSub::getAuthSubTokenUri($next, SCOPE, $secure, $session,
+                                             $authSubHandler);
+    $authSubURL .= '&permission=' . $permission;
+    echo '<a href="' . $authSubURL . '">Link your Google Health Account</a>';
+    exit();
+  }
+
+  $client = new Zend_Gdata_HttpClient();
+  $client->setAuthSubPrivateKeyFile(HEALTH_PRIVATE_KEY, null, true);
+
+  // Convert an AuthSub one-time token into a session token if needed
+  if ($singleUseToken && !$sessionToken) {
+    $sessionToken =
+      Zend_Gdata_AuthSub::getAuthSubSessionToken($singleUseToken, $client);
+    $_SESSION['sessionToken'] = $sessionToken;
+  }
+  $client->setAuthSubToken($sessionToken);
+  return $client;
+}
+
+function getTokenInfo($client) {
+  $sessionToken = $client->getAuthSubToken();
+  return Zend_Gdata_AuthSub::getAuthSubTokenInfo($sessionToken, $client);
+}
+
+function revokeToken($client) {
+  $sessionToken = $client->getAuthSubToken();
+  return Zend_Gdata_AuthSub::AuthSubRevokeToken($sessionToken, $client);
+}
+
+/** Prettifies an XML string into a human-readable and indented work of art
+ *  @param string $xml The XML as a string
+ *  @param boolean $html_output True if the output should be escaped (for use in HTML)
+ */
+function xmlpp($xml, $html_output=true) {
+  $xml_obj = new SimpleXMLElement($xml);
+  $level = 4;
+  $indent = 0; // current indentation level
+  $pretty = array();
+
+  // get an array containing each XML element
+  $xml = explode("\n", preg_replace('/>\s*</', ">\n<", $xml_obj->asXML()));
+
+  // shift off opening XML tag if present
+  if (count($xml) && preg_match('/^<\?\s*xml/', $xml[0])) {
+    $pretty[] = array_shift($xml);
+  }
+
+  foreach ($xml as $el) {
+    if (preg_match('/^<([\w])+[^>\/]*>$/U', $el)) {
+      // opening tag, increase indent
+      $pretty[] = str_repeat(' ', $indent) . $el;
+      $indent += $level;
+    } else {
+      if (preg_match('/^<\/.+>$/', $el)) {
+        $indent -= $level;  // closing tag, decrease indent
+      }
+      if ($indent < 0) {
+        $indent += $level;
+      }
+      $pretty[] = str_repeat(' ', $indent) . $el;
+    }
+  }
+  $xml = implode("\n", $pretty);
+  return ($html_output) ? htmlentities($xml) : $xml;
+}
+?>

+ 386 - 0
demos/Zend/Gdata/InstallationChecker.php

@@ -0,0 +1,386 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Simple class to verify that the server that this is run on has a correct
+ * installation of the Zend Framework Gdata component.
+ */
+class InstallationChecker {
+
+    const CSS_WARNING = '.warning { color: #fff; background-color: #AF0007;}';
+    const CSS_SUCCESS = '.success { color: #000; background-color: #69FF4F;}';
+    const CSS_ERROR = '.error { color: #fff; background-color: #FF9FA3;}';
+    const PHP_EXTENSION_ERRORS = 'PHP Extension Errors';
+    const PHP_MANUAL_LINK_FRAGMENT = 'http://us.php.net/manual/en/book.';
+    const PHP_REQUIREMENT_CHECKER_ID = 'PHP Requirement checker v0.1';
+    const SSL_CAPABILITIES_ERRORS = 'SSL Capabilities Errors';
+    const YOUTUBE_API_CONNECTIVITY_ERRORS = 'YouTube API Connectivity Errors';
+    const ZEND_GDATA_INSTALL_ERRORS = 'Zend Framework Installation Errors';
+    const ZEND_SUBVERSION_URI = 'http://framework.zend.com/download/subversion';
+
+    private static $REQUIRED_EXTENSIONS = array(
+        'ctype', 'dom', 'libxml', 'spl', 'standard', 'openssl');
+
+    private $_allErrors = array(
+        self::PHP_EXTENSION_ERRORS => array(
+            'tested' => false, 'errors' => null),
+        self::ZEND_GDATA_INSTALL_ERRORS => array(
+            'tested' => false, 'errors' => null),
+        self::SSL_CAPABILITIES_ERRORS => array(
+            'tested' => false, 'errors' => null),
+        self::YOUTUBE_API_CONNECTIVITY_ERRORS => array(
+            'tested' => false, 'errors' => null)
+            );
+
+    private $_sapiModeCLI = null;
+
+    /**
+     * Create a new InstallationChecker object and run verifications.
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->determineIfInCLIMode();
+        $this->runAllVerifications();
+        $this->outputResults();
+    }
+
+    /**
+     * Set the sapiModeCLI variable to true if we are running CLI mode.
+     *
+     * @return void
+     */
+    private function determineIfInCLIMode()
+    {
+        if (php_sapi_name() == 'cli') {
+            $this->_sapiModeCLI = true;
+        }
+    }
+
+    /**
+     * Getter for sapiModeCLI variable.
+     *
+     * @return boolean True if we are running in CLI mode.
+     */
+    public function runningInCLIMode()
+    {
+        if ($this->_sapiModeCLI) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Run verifications, stopping at each step if there is a failure.
+     *
+     * @return void
+     */
+    public function runAllVerifications()
+    {
+        if (!$this->validatePHPExtensions()) {
+            return;
+        }
+        if (!$this->validateZendFrameworkInstallation()) {
+            return;
+        }
+        if (!$this->testSSLCapabilities()) {
+            return;
+        }
+        if (!$this->validateYouTubeAPIConnectivity()) {
+            return;
+        }
+    }
+
+    /**
+     * Validate that the required PHP Extensions are installed and available.
+     *
+     * @return boolean False if there were errors.
+     */
+    private function validatePHPExtensions()
+    {
+        $phpExtensionErrors = array();
+        foreach (self::$REQUIRED_EXTENSIONS as $requiredExtension) {
+            if (!extension_loaded($requiredExtension)) {
+                $requiredExtensionError = $requiredExtension .
+                    ' extension missing';
+                $documentationLink = null;
+                if ($requiredExtension != 'standard') {
+                    $documentationLink = self::PHP_MANUAL_LINK_FRAGMENT .
+                        $requiredExtension . '.php';
+                        $documentationLink =
+                            $this->checkAndAddHTMLLink($documentationLink);
+                } else {
+                    $documentationLink = self::PHP_MANUAL_LINK_FRAGMENT .
+                        'spl.php';
+                    $documentationLink =
+                        $this->checkAndAddHTMLLink($documentationLink);
+                }
+
+                if ($documentationLink) {
+                    $phpExtensionErrors[] = $requiredExtensionError .
+                        ' - refer to ' . $documentationLink;
+                }
+            }
+        }
+        $this->_allErrors[self::PHP_EXTENSION_ERRORS]['tested'] = true;
+        if (count($phpExtensionErrors) > 0) {
+            $this->_allErrors[self::PHP_EXTENSION_ERRORS]['errors'] =
+                $phpExtensionErrors;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Validate that the Gdata component of Zend Framework is installed
+     * properly. Also checks that the required YouTube API helper methods are
+     * found.
+     *
+     * @return boolean False if there were errors.
+     */
+    private function validateZendFrameworkInstallation()
+    {
+        $zendFrameworkInstallationErrors = array();
+        $zendLoaderPresent = false;
+        try {
+            $zendLoaderPresent = @fopen('Zend/Loader.php', 'r', true);
+        } catch (Exception $e) {
+            $zendFrameworkInstallationErrors[] = 'Exception thrown trying to ' .
+                'access Zend/Loader.php using \'use_include_path\' = true ' .
+                'Make sure you include the Zend Framework in your ' .
+                'include_path which currently contains: "' .
+                ini_get('include_path') . '"';
+        }
+
+        if ($zendLoaderPresent) {
+            @fclose($zendLoaderPresent);
+            require_once('Zend/Loader.php');
+            require_once('Zend/Version.php');
+            Zend_Loader::loadClass('Zend_Gdata_YouTube');
+            Zend_Loader::loadClass('Zend_Gdata_YouTube_VideoEntry');
+            $yt = new Zend_Gdata_YouTube();
+            $videoEntry = $yt->newVideoEntry();
+            if (!method_exists($videoEntry, 'setVideoTitle')) {
+                $zendFrameworkMessage = 'Your version of the ' .
+                    'Zend Framework ' . Zend_Version::VERSION . ' is too old' .
+                    ' to run the YouTube demo application and does not' .
+                    ' contain the new helper methods. Please check out a' .
+                    ' newer version from Zend\'s repository: ' .
+                    checkAndAddHTMLLink(self::ZEND_SUBVERSION_URI);
+                $zendFrameworkInstallationErrors[] = $zendFrameworkMessage;
+            }
+        } else {
+            if (count($zendFrameworkInstallationErrors) < 1) {
+                $zendFrameworkInstallationErrors[] = 'Exception thrown trying' .
+                    ' to access Zend/Loader.php using \'use_include_path\' =' .
+                    ' true. Make sure you include Zend Framework in your' .
+                    ' include_path which currently contains: ' .
+                    ini_get('include_path');
+            }
+        }
+
+        $this->_allErrors[self::ZEND_GDATA_INSTALL_ERRORS]['tested'] = true;
+
+        if (count($zendFrameworkInstallationErrors) > 0) {
+            $this->_allErrors[self::ZEND_GDATA_INSTALL_ERRORS]['errors'] =
+                $zendFrameworkInstallationErrors;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Create HTML link from an input string if not in CLI mode.
+     *
+     * @param string The error message to be converted to a link.
+     * @return string Either the original error message or an HTML version.
+     */
+    private function checkAndAddHTMLLink($inputString) {
+        if (!$this->runningInCLIMode()) {
+            return $this->makeHTMLLink($inputString);
+        } else {
+            return $inputString;
+        }
+    }
+
+    /**
+     * Create an HTML link from a string.
+     *
+     * @param string The string to be made into link text and anchor target.
+     * @return string HTML link.
+     */
+    private function makeHTMLLink($inputString)
+    {
+        return '<a href="'. $inputString . '" target="_blank">' .
+            $inputString . '</a>';
+    }
+
+    /**
+     * Validate that SSL Capabilities are available.
+     *
+     * @return boolean False if there were errors.
+     */
+    private function testSSLCapabilities()
+    {
+        $sslCapabilitiesErrors = array();
+        require_once 'Zend/Loader.php';
+        Zend_Loader::loadClass('Zend_Http_Client');
+
+        $httpClient = new Zend_Http_Client(
+            'https://www.google.com/accounts/AuthSubRequest');
+        $response = $httpClient->request();
+        $this->_allErrors[self::SSL_CAPABILITIES_ERRORS]['tested'] = true;
+
+        if ($response->isError()) {
+            $sslCapabilitiesErrors[] = 'Response from trying to access' .
+                ' \'https://www.google.com/accounts/AuthSubRequest\' ' .
+                $response->getStatus() . ' - ' . $response->getMessage();
+        }
+
+        if (count($sslCapabilitiesErrors) > 0) {
+            $this->_allErrors[self::SSL_CAPABILITIES_ERRORS]['errors'] =
+                $sslCapabilitiesErrors;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Validate that we can connect to the YouTube API.
+     *
+     * @return boolean False if there were errors.
+     */
+    private function validateYouTubeAPIConnectivity()
+    {
+        $connectivityErrors = array();
+        require_once 'Zend/Loader.php';
+        Zend_Loader::loadClass('Zend_Gdata_YouTube');
+        $yt = new Zend_Gdata_YouTube();
+        $topRatedFeed = $yt->getTopRatedVideoFeed();
+        if ($topRatedFeed instanceof Zend_Gdata_YouTube_VideoFeed) {
+            if ($topRatedFeed->getTotalResults()->getText() < 1) {
+                $connectivityErrors[] = 'There was less than 1 video entry' .
+                    ' in the \'Top Rated Video Feed\'';
+            }
+        } else {
+            $connectivityErrors[] = 'The call to \'getTopRatedVideoFeed()\' ' .
+                'did not result in a Zend_Gdata_YouTube_VideoFeed object';
+        }
+
+        $this->_allErrors[self::YOUTUBE_API_CONNECTIVITY_ERRORS]['tested'] =
+            true;
+        if (count($connectivityErrors) > 0) {
+            $this->_allErrors[self::YOUTUBE_API_CONNECTIVITY_ERRORS]['tested'] =
+                $connectivityErrors;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Dispatch a call to outputResultsInHTML or outputResultsInText pending
+     * the current SAPI mode.
+     *
+     * @return void
+     */
+    public function outputResults()
+    {
+        if ($this->_sapiModeCLI) {
+          print $this->getResultsInText();
+        } else {
+          print $this->getResultsInHTML();
+        }
+    }
+
+
+    /**
+     * Return a string representing the results of the verifications.
+     *
+     * @return string A string representing the results.
+     */
+    private function getResultsInText()
+    {
+        $output = "== Ran PHP Installation Checker using CLI ==\n";
+
+        $error_count = 0;
+        foreach($this->_allErrors as $key => $value) {
+            $output .= $key . ' -- ';
+            if (($value['tested'] == true) && (count($value['errors']) == 0)) {
+                $output .= "No errors found\n";
+            } elseif ($value['tested'] == true) {
+                $output .= "Tested\n";
+                $error_count = 0;
+                foreach ($value['errors'] as $error) {
+                    $output .= "Error number: " . $error_count . "\n--" .
+                        $error . "\n";
+                }
+            } else {
+                $output .= "Not tested\n";
+            }
+            $error_count++;
+        }
+        return $output;
+    }
+
+    /**
+     * Return an HTML table representing the results of the verifications.
+     *
+     * @return string An HTML string representing the results.
+     */
+    private function getResultsInHTML()
+    {
+        $html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " .
+            "\"http://www.w3.org/TR/html4/strict.dtd\">\n".
+            "<html><head>\n<title>PHP Installation Checker</title>\n" .
+            "<style type=\"text/css\">\n" .
+            self::CSS_WARNING . "\n" .
+            self::CSS_SUCCESS . "\n" .
+            self::CSS_ERROR . "\n" .
+            "</style></head>\n" .
+            "<body>\n<table class=\"verification_table\">" .
+            "<caption>Ran PHP Installation Checker on " .
+            gmdate('c') . "</caption>\n";
+
+        $error_count = 0;
+        foreach($this->_allErrors as $key => $value) {
+            $html .= "<tr><td class=\"verification_type\">" . $key . "</td>";
+            if (($value['tested'] == true) && (count($value['errors']) == 0)) {
+                $html .= "<td class=\"success\">Tested</td></tr>\n" .
+                    "<tr><td colspan=\"2\">No errors found</td></tr>\n";
+            } elseif ($value['tested'] == true) {
+                $html .= "<td class=\"warning\">Tested</td></tr>\n";
+                $error_count = 0;
+                foreach ($value['errors'] as $error) {
+                    $html .= "<tr><td class=\"error\">" . $error_count . "</td>" .
+                        "<td class=\"error\">" . $error . "</td></tr>\n";
+                }
+            } else {
+                $html .= "<td class=\"warning\">Not tested</td></tr>\n";
+            }
+            $error_count++;
+        }
+        $html .= "</body></html>";
+        return $html;
+    }
+}
+
+$installationChecker = new InstallationChecker();

+ 226 - 0
demos/Zend/Gdata/MyLibrary/demo.php

@@ -0,0 +1,226 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata_Books
+ */
+Zend_Loader::loadClass('Zend_Gdata_Books');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+/**
+ * @see Zend_Gdata_App_AuthException
+ */
+Zend_Loader::loadClass('Zend_Gdata_App_AuthException');
+
+
+class SimpleDemo {
+    /**
+     * Constructor
+     *
+     * @param  string $email
+     * @param  string $password
+     * @return void
+     */
+    public function __construct($email, $password)
+    {
+        try {
+          $client = Zend_Gdata_ClientLogin::getHttpClient($email, $password, 
+                    Zend_Gdata_Books::AUTH_SERVICE_NAME);
+        } catch (Zend_Gdata_App_AuthException $ae) {
+          exit("Error: ". $ae->getMessage() ."\nCredentials provided were ".
+               "email: [$email] and password [$password].\n");
+        }
+        $this->gdClient = new Zend_Gdata_Books($client);
+    }
+
+    /**
+     * Print the content of a feed
+     *
+     * @param  Zend_Gdata_Gbase_Feed $feed
+     * @return void
+     */
+    public function printFeed($feed)
+    {
+        $i = 0;
+        foreach($feed as $entry) {
+            $titles = $entry->getTitles();
+            $rating = $entry->getRating();
+            if (count($titles)) {                
+                if (!is_object($rating)) {
+                    $rating_str = "?";
+                } else {
+                    $rating_str = $rating->getAverage();
+                }
+                print $i." ".$titles[0]->getText().
+                    ", Rating: ".$rating_str."\n";
+                $i++;
+            }
+        }
+    }
+
+    /**
+     * List books in the My library feed
+     *
+     * @return void
+     */
+    public function listLibrary()
+    {
+        $feed = $this->gdClient->getUserLibraryFeed();
+        print "== Books in my library ==\n";
+        $this->printFeed($feed);
+        print "\n";
+    }
+
+    /**
+     * List books in the annotation feed.
+     *
+     * @return void
+     */
+    public function listReviewed()
+    {
+        $feed = $this->gdClient->getUserLibraryFeed(
+            Zend_Gdata_Books::MY_ANNOTATION_FEED_URI);
+        print "== Books I annotated ==\n";
+        $this->printFeed($feed);
+        print "\n";
+    }
+
+    /**
+     * Add an arbitrary book to the library feed.
+     *
+     * @param string $volumeId Volume to the library
+     * @return void
+     */
+    public function addBookToLibrary($volumeId)
+    {
+        $entry = new Zend_Gdata_Books_VolumeEntry();
+        $entry->setId(
+            new Zend_Gdata_App_Extension_Id($volumeId));
+        print "Inserting ".$volumeId."\n\n";
+        return $this->gdClient->insertVolume($entry);        
+    }
+
+    /**
+     * Add an arbitrary book to the library feed.
+     *
+     * @param string $volumeId Volume to add a rating to
+     * @param float $rating Numeric rating from 0 to 5 
+     * @return void
+     */
+    public function addRating($volumeId, $rating)
+    {
+        $entry = new Zend_Gdata_Books_VolumeEntry();
+        $entry->setId(
+            new Zend_Gdata_App_Extension_Id($volumeId));
+        $entry->setRating(
+            new Zend_Gdata_Extension_Rating($rating, "0", 5, 1));
+        print "Inserting a rating of ".$rating." for ".$volumeId."\n\n";
+        return $this->gdClient->insertVolume($entry,
+            Zend_Gdata_Books::MY_ANNOTATION_FEED_URI);        
+    }
+
+    /**
+     * Remove an an arbitrary book from a feed (either remove
+     * from library feed or remove the annotations from annotation
+     * feed).
+     *
+     * @param Zend_Gdata_Books_VolumeEntry $entry
+     * @return void
+     */
+    public function removeBook($entry)
+    {
+        print "Deleting ".$entry->getId()->getText()."\n\n";
+        $this->gdClient->deleteVolume($entry);        
+    }
+
+    /**
+     * Main logic for the demo.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $test_volume = "8YEAAAAAYAAJ";
+
+        // Playing with the library feed
+        $this->listLibrary();
+
+        $entry = $this->addBookToLibrary($test_volume);
+        $this->listLibrary();
+
+        $this->removeBook($entry);
+        $this->listLibrary();
+
+        // Playing with the annotation feed
+        $this->listReviewed();
+
+        $entry = $this->addRating($test_volume, 4.0);
+        $this->listReviewed();
+
+        $this->removeBook($entry);
+        $this->listReviewed();
+    }
+}
+
+/**
+ * getInput
+ *
+ * @param  string $text
+ * @return string
+ */
+function getInput($text)
+{
+    echo $text.': ';
+    return trim(fgets(STDIN));
+}
+
+echo "Books Gdata API - my library demo\n\n";
+$email = null;
+$pass = null;
+
+// process command line options
+foreach ($argv as $argument) {
+    $argParts = split('=', $argument);
+    if ($argParts[0] == '--email') {
+        $email = $argParts[1];
+    } else if ($argParts[0] == '--pass') {
+        $pass = $argParts[1];
+    }
+}
+
+if (($email == null) || ($pass == null)) {
+    $email = getInput(
+        "Please enter your email address [example: username@gmail.com]");
+    $pass = getInput(
+        "Please enter your password [example: mypassword]");
+}
+
+$demo = new SimpleDemo($email, $pass);
+$demo->run();

+ 904 - 0
demos/Zend/Gdata/Photos.php

@@ -0,0 +1,904 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the Photos data API.  Utilizes the 
+ * Zend Framework Gdata components to communicate with the Google API.
+ * 
+ * Requires the Zend Framework Gdata components and PHP >= 5.1.4
+ *
+ * You can run this sample from a web browser.
+ *
+ * NOTE: You must ensure that Zend Framework is in your PHP include
+ * path.  You can do this via php.ini settings, or by modifying the 
+ * argument to set_include_path in the code below.
+ *
+ * NOTE: As this is sample code, not all of the functions do full error
+ * handling.
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_AuthSub
+ */
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+
+/**
+ * @see Zend_Gdata_Photos
+ */
+Zend_Loader::loadClass('Zend_Gdata_Photos');
+
+/**
+ * @see Zend_Gdata_Photos_UserQuery
+ */
+Zend_Loader::loadClass('Zend_Gdata_Photos_UserQuery');
+
+/**
+ * @see Zend_Gdata_Photos_AlbumQuery
+ */
+Zend_Loader::loadClass('Zend_Gdata_Photos_AlbumQuery');
+
+/**
+ * @see Zend_Gdata_Photos_PhotoQuery
+ */
+Zend_Loader::loadClass('Zend_Gdata_Photos_PhotoQuery');
+
+/**
+ * @see Zend_Gdata_App_Extension_Category
+ */
+Zend_Loader::loadClass('Zend_Gdata_App_Extension_Category');
+
+session_start();
+
+
+/**
+ * Adds a new photo to the specified album
+ *
+ * @param  Zend_Http_Client $client  The authenticated client
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @param  array            $photo   The uploaded photo
+ * @return void
+ */
+function addPhoto($client, $user, $albumId, $photo)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $fd = $photos->newMediaFileSource($photo["tmp_name"]);
+    $fd->setContentType($photo["type"]);
+    
+    $entry = new Zend_Gdata_Photos_PhotoEntry();
+    $entry->setMediaSource($fd);
+    $entry->setTitle($photos->newTitle($photo["name"]));
+    
+    $albumQuery = new Zend_Gdata_Photos_AlbumQuery;
+    $albumQuery->setUser($user);
+    $albumQuery->setAlbumId($albumId);
+    
+    $albumEntry = $photos->getAlbumEntry($albumQuery);
+    
+    $result = $photos->insertPhotoEntry($entry, $albumEntry);
+    if ($result) {
+        outputAlbumFeed($client, $user, $albumId);
+    } else {
+        echo "There was an issue with the file upload.";
+    }
+}
+
+/**
+ * Deletes the specified photo
+ *
+ * @param  Zend_Http_Client $client  The authenticated client
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @param  integer          $photoId The photo's id
+ * @return void
+ */
+function deletePhoto($client, $user, $albumId, $photoId)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $photoQuery = new Zend_Gdata_Photos_PhotoQuery;
+    $photoQuery->setUser($user);
+    $photoQuery->setAlbumId($albumId);
+    $photoQuery->setPhotoId($photoId);
+    $photoQuery->setType('entry');
+
+    $entry = $photos->getPhotoEntry($photoQuery);
+    
+    $photos->deletePhotoEntry($entry, true);
+    
+    outputAlbumFeed($client, $user, $albumId);
+}
+
+/**
+ * Adds a new album to the specified user's album
+ *
+ * @param  Zend_Http_Client $client The authenticated client
+ * @param  string           $user   The user's account name
+ * @param  string           $name   The name of the new album
+ * @return void
+ */
+function addAlbum($client, $user, $name)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $entry = new Zend_Gdata_Photos_AlbumEntry();
+    $entry->setTitle($photos->newTitle($name));
+    
+    $result = $photos->insertAlbumEntry($entry);
+    if ($result) {
+        outputUserFeed($client, $user);
+    } else {
+        echo "There was an issue with the album creation.";
+    }
+}
+
+/**
+ * Deletes the specified album
+ *
+ * @param  Zend_Http_Client $client  The authenticated client
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @return void
+ */
+function deleteAlbum($client, $user, $albumId)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $albumQuery = new Zend_Gdata_Photos_AlbumQuery;
+    $albumQuery->setUser($user);
+    $albumQuery->setAlbumId($albumId);
+    $albumQuery->setType('entry');
+
+    $entry = $photos->getAlbumEntry($albumQuery);
+    
+    $photos->deleteAlbumEntry($entry, true);
+    
+    outputUserFeed($client, $user);
+}
+
+/**
+ * Adds a new comment to the specified photo
+ *
+ * @param  Zend_Http_Client $client  The authenticated client
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @param  integer          $photoId The photo's id
+ * @param  string           $comment The comment to add
+ * @return void
+ */
+function addComment($client, $user, $album, $photo, $comment)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $entry = new Zend_Gdata_Photos_CommentEntry();
+    $entry->setTitle($photos->newTitle($comment));
+    $entry->setContent($photos->newContent($comment));
+    
+    $photoQuery = new Zend_Gdata_Photos_PhotoQuery;
+    $photoQuery->setUser($user);
+    $photoQuery->setAlbumId($album);
+    $photoQuery->setPhotoId($photo);
+    $photoQuery->setType('entry');
+
+    $photoEntry = $photos->getPhotoEntry($photoQuery);
+    
+    $result = $photos->insertCommentEntry($entry, $photoEntry);
+    if ($result) {
+        outputPhotoFeed($client, $user, $album, $photo);
+    } else {
+        echo "There was an issue with the comment creation.";
+    }
+}
+
+/**
+ * Deletes the specified comment
+ *
+ * @param  Zend_Http_Client $client    The authenticated client
+ * @param  string           $user      The user's account name
+ * @param  integer          $albumId   The album's id
+ * @param  integer          $photoId   The photo's id
+ * @param  integer          $commentId The comment's id
+ * @return void
+ */
+function deleteComment($client, $user, $albumId, $photoId, $commentId)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $photoQuery = new Zend_Gdata_Photos_PhotoQuery;
+    $photoQuery->setUser($user);
+    $photoQuery->setAlbumId($albumId);
+    $photoQuery->setPhotoId($photoId);
+    $photoQuery->setType('entry');
+    
+    $path = $photoQuery->getQueryUrl() . '/commentid/' . $commentId;
+    
+    $entry = $photos->getCommentEntry($path);
+    
+    $photos->deleteCommentEntry($entry, true);
+    
+    outputPhotoFeed($client, $user, $albumId, $photoId);
+}
+
+/**
+ * Adds a new tag to the specified photo
+ *
+ * @param  Zend_Http_Client $client The authenticated client
+ * @param  string           $user   The user's account name
+ * @param  integer          $album  The album's id
+ * @param  integer          $photo  The photo's id
+ * @param  string           $tag    The tag to add to the photo
+ * @return void
+ */
+function addTag($client, $user, $album, $photo, $tag)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $entry = new Zend_Gdata_Photos_TagEntry();
+    $entry->setTitle($photos->newTitle($tag));
+    
+    $photoQuery = new Zend_Gdata_Photos_PhotoQuery;
+    $photoQuery->setUser($user);
+    $photoQuery->setAlbumId($album);
+    $photoQuery->setPhotoId($photo);
+    $photoQuery->setType('entry');
+
+    $photoEntry = $photos->getPhotoEntry($photoQuery);
+    
+    $result = $photos->insertTagEntry($entry, $photoEntry);
+    if ($result) {
+        outputPhotoFeed($client, $user, $album, $photo);
+    } else {
+        echo "There was an issue with the tag creation.";
+    }
+}
+
+/**
+ * Deletes the specified tag
+ *
+ * @param  Zend_Http_Client $client     The authenticated client
+ * @param  string           $user       The user's account name
+ * @param  integer          $albumId    The album's id
+ * @param  integer          $photoId    The photo's id
+ * @param  string           $tagContent The name of the tag to be deleted
+ * @return void
+ */
+function deleteTag($client, $user, $albumId, $photoId, $tagContent)
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $photoQuery = new Zend_Gdata_Photos_PhotoQuery;
+    $photoQuery->setUser($user);
+    $photoQuery->setAlbumId($albumId);
+    $photoQuery->setPhotoId($photoId);
+    $query = $photoQuery->getQueryUrl() . "?kind=tag";
+    
+    $photoFeed = $photos->getPhotoFeed($query);
+    
+    foreach ($photoFeed as $entry) {
+        if ($entry instanceof Zend_Gdata_Photos_TagEntry) {
+            if ($entry->getContent() == $tagContent) {
+                $tagEntry = $entry;
+            }
+        }
+    }
+    
+    $photos->deleteTagEntry($tagEntry, true);
+    
+    outputPhotoFeed($client, $user, $albumId, $photoId);
+}
+
+/**
+ * Returns the path to the current script, without any query params
+ *
+ * Env variables used:
+ * $_SERVER['PHP_SELF']
+ *
+ * @return string Current script path
+ */
+function getCurrentScript() 
+{
+    global $_SERVER;
+    return $_SERVER["PHP_SELF"];
+}
+
+/**
+ * Returns the full URL of the current page, based upon env variables
+ * 
+ * Env variables used:
+ * $_SERVER['HTTPS'] = (on|off|)
+ * $_SERVER['HTTP_HOST'] = value of the Host: header
+ * $_SERVER['SERVER_PORT'] = port number (only used if not http/80,https/443)
+ * $_SERVER['REQUEST_URI'] = the URI after the method of the HTTP request
+ *
+ * @return string Current URL
+ */
+function getCurrentUrl() 
+{
+    global $_SERVER;
+
+    /**
+     * Filter php_self to avoid a security vulnerability.
+     */
+    $php_request_uri = htmlentities(substr($_SERVER['REQUEST_URI'], 0,
+    strcspn($_SERVER['REQUEST_URI'], "\n\r")), ENT_QUOTES);
+
+    if (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on') {
+        $protocol = 'https://';
+    } else {
+        $protocol = 'http://';
+    }
+    $host = $_SERVER['HTTP_HOST'];
+    if ($_SERVER['SERVER_PORT'] != '' &&
+        (($protocol == 'http://' && $_SERVER['SERVER_PORT'] != '80') ||
+        ($protocol == 'https://' && $_SERVER['SERVER_PORT'] != '443'))) {
+            $port = ':' . $_SERVER['SERVER_PORT'];
+    } else {
+        $port = '';
+    }
+    return $protocol . $host . $port . $php_request_uri;
+}
+
+/**
+ * Returns the AuthSub URL which the user must visit to authenticate requests 
+ * from this application.
+ *
+ * Uses getCurrentUrl() to get the next URL which the user will be redirected
+ * to after successfully authenticating with the Google service.
+ *
+ * @return string AuthSub URL
+ */
+function getAuthSubUrl() 
+{
+    $next = getCurrentUrl();
+    $scope = 'http://picasaweb.google.com/data';
+    $secure = false;
+    $session = true;
+    return Zend_Gdata_AuthSub::getAuthSubTokenUri($next, $scope, $secure, 
+        $session);
+}
+
+/**
+ * Outputs a request to the user to login to their Google account, including
+ * a link to the AuthSub URL.
+ * 
+ * Uses getAuthSubUrl() to get the URL which the user must visit to authenticate
+ *
+ * @return void
+ */
+function requestUserLogin($linkText) 
+{
+    $authSubUrl = getAuthSubUrl();
+    echo "<a href=\"{$authSubUrl}\">{$linkText}</a>"; 
+}
+
+/**
+ * Returns a HTTP client object with the appropriate headers for communicating
+ * with Google using AuthSub authentication.
+ *
+ * Uses the $_SESSION['sessionToken'] to store the AuthSub session token after
+ * it is obtained.  The single use token supplied in the URL when redirected 
+ * after the user succesfully authenticated to Google is retrieved from the 
+ * $_GET['token'] variable.
+ *
+ * @return Zend_Http_Client
+ */
+function getAuthSubHttpClient() 
+{
+    global $_SESSION, $_GET;
+    if (!isset($_SESSION['sessionToken']) && isset($_GET['token'])) {
+        $_SESSION['sessionToken'] = 
+            Zend_Gdata_AuthSub::getAuthSubSessionToken($_GET['token']);
+    } 
+    $client = Zend_Gdata_AuthSub::getHttpClient($_SESSION['sessionToken']);
+    return $client;
+}
+
+/**
+ * Processes loading of this sample code through a web browser.  Uses AuthSub
+ * authentication and outputs a list of a user's albums if succesfully 
+ * authenticated.
+ *
+ * @return void
+ */
+function processPageLoad() 
+{
+    global $_SESSION, $_GET;
+    if (!isset($_SESSION['sessionToken']) && !isset($_GET['token'])) {
+        requestUserLogin('Please login to your Google Account.');
+    } else {
+        $client = getAuthSubHttpClient();
+        if (!empty($_REQUEST['command'])) {
+            switch ($_REQUEST['command']) {
+                case 'retrieveSelf':
+                    outputUserFeed($client, "default");
+                    break;
+                case 'retrieveUser':
+                outputUserFeed($client, $_REQUEST['user']);
+                    break;
+                case 'retrieveAlbumFeed':
+                    outputAlbumFeed($client, $_REQUEST['user'], $_REQUEST['album']);
+                    break;
+                case 'retrievePhotoFeed':
+                    outputPhotoFeed($client, $_REQUEST['user'], $_REQUEST['album'],
+                        $_REQUEST['photo']);
+                    break;
+            }
+        }
+
+        // Now we handle the potentially destructive commands, which have to 
+        // be submitted by POST only.
+        if (!empty($_POST['command'])) {
+            switch ($_POST['command']) {
+                case 'addPhoto':
+                    addPhoto($client, $_POST['user'], $_POST['album'], $_FILES['photo']);
+                    break;
+                case 'deletePhoto':
+                    deletePhoto($client, $_POST['user'], $_POST['album'],
+                        $_POST['photo']);
+                    break;
+                case 'addAlbum':
+                    addAlbum($client, $_POST['user'], $_POST['name']);
+                    break;
+                case 'deleteAlbum':
+                    deleteAlbum($client, $_POST['user'], $_POST['album']);
+                    break;
+                case 'addComment':
+                    addComment($client, $_POST['user'], $_POST['album'], $_POST['photo'],
+                        $_POST['comment']);
+                    break;
+                case 'addTag':
+                    addTag($client, $_POST['user'], $_POST['album'], $_POST['photo'],
+                        $_POST['tag']);
+                    break;
+                case 'deleteComment':
+                    deleteComment($client, $_POST['user'], $_POST['album'],
+                        $_POST['photo'], $_POST['comment']);
+                    break;
+                case 'deleteTag':
+                    deleteTag($client, $_POST['user'], $_POST['album'], $_POST['photo'],
+                        $_POST['tag']);
+                    break;
+              default:
+                    break;
+          }
+        }
+
+        // If a menu parameter is available, display a submenu.
+        if (!empty($_REQUEST['menu'])) {
+            switch ($_REQUEST['menu']) {
+              case 'user':
+                displayUserMenu();
+                    break;
+                case 'photo':
+                    displayPhotoMenu();
+                    break;
+            case 'album':
+              displayAlbumMenu();
+                    break;
+            case 'logout':
+              logout();
+                    break;
+            default:
+                header('HTTP/1.1 400 Bad Request');
+                echo "<h2>Invalid menu selection.</h2>\n";
+                echo "<p>Please check your request and try again.</p>";
+          }
+        }
+        
+        if (empty($_REQUEST['menu']) && empty($_REQUEST['command'])) {
+            displayMenu();
+        }
+    }
+}
+
+/**
+ * Displays the main menu, allowing the user to select from a list of actions.
+ *
+ * @return void
+ */
+function displayMenu()
+{
+?>
+<h2>Main Menu</h2>
+
+<p>Welcome to the Photos API demo page. Please select 
+    from one of the following four options to fetch information.</p>
+
+    <ul>
+        <li><a href="?command=retrieveSelf">Your Feed</a></li>
+        <li><a href="?menu=user">User Menu</a></li>
+        <li><a href="?menu=photo">Photos Menu</a></li>
+        <li><a href="?menu=album">Albums Menu</a></li>
+    </ul>
+<?php
+}
+
+/**
+ * Outputs an HTML link to return to the previous page.
+ *
+ * @return void
+ */
+function displayBackLink()
+{
+    echo "<br><br>";
+    echo "<a href='javascript: history.go(-1);'><< Back</a>";
+}
+
+/**
+ * Displays the user menu, allowing the user to request a specific user's feed.
+ *
+ * @return void
+ */
+function displayUserMenu()
+{
+?>
+<h2>User Menu</h2>
+
+<div class="menuForm">
+    <form method="get" accept-charset="utf-8">
+        <h3 class='nopad'>Retrieve User Feed</h3>
+        <p>Retrieve the feed for an existing user.</p>
+        <p>
+            <input type="hidden" name="command" value="retrieveUser" />
+            <label for="user">Username: </label>
+            <input type="text" name="user" value="" /><br />
+        </p>
+
+        <p><input type="submit" value="Retrieve User &rarr;"></p>
+    </form>
+</div>
+<?php
+
+    displayBackLink();
+}
+
+/**
+ * Displays the photo menu, allowing the user to request a specific photo's feed.
+ *
+ * @return void
+ */
+function displayPhotoMenu()
+{
+?>
+<h2>Photo Menu</h2>
+
+<div class="menuForm">
+    <form method="get" accept-charset="utf-8">
+        <h3 class='nopad'>Retrieve Photo Feed</h3>
+        <p>Retrieve the feed for an existing photo.</p>
+        <p>
+            <input type="hidden" name="command" value="retrievePhotoFeed" />
+            <label for="user">User: </label>
+            <input type="text" name="user" value="" /><br />
+            <label for="album">Album ID: </label>
+            <input type="text" name="album" value="" /><br />
+            <label for="photoid">Photo ID: </label>
+            <input type="text" name="photo" value="" /><br />
+        </p>
+
+        <p><input type="submit" value="Retrieve Photo Feed &rarr;"></p>
+    </form>
+</div>
+<?php
+
+    displayBackLink();
+}
+
+/**
+ * Displays the album menu, allowing the user to request a specific album's feed.
+ *
+ * @return void
+ */
+function displayAlbumMenu()
+{
+?>
+<h2>Album Menu</h2>
+
+<div class="menuForm">
+    <form method="get" accept-charset="utf-8">
+        <h3 class='nopad'>Retrieve Album Feed</h3>
+        <p>Retrieve the feed for an existing album.</p>
+        <p>
+            <input type="hidden" name="command" value="retrieveAlbumFeed" />
+            <label for="user">User: </label>
+                    <input type="text" name="user" value="" /><br />
+                    <label for="album">Album ID: </label>
+                    <input type="text" name="album" value="" /><br />
+        </p>
+
+        <p><input type="submit" value="Retrieve Album Feed &rarr;"></p>
+    </form>
+</div>
+<?php
+
+    displayBackLink();
+}
+
+/** 
+ * Outputs an HTML unordered list (ul), with each list item representing an
+ * album in the user's feed.
+ *
+ * @param  Zend_Http_Client $client The authenticated client object
+ * @param  string           $user   The user's account name
+ * @return void
+ */
+function outputUserFeed($client, $user) 
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $query = new Zend_Gdata_Photos_UserQuery();
+    $query->setUser($user);
+
+    $userFeed = $photos->getUserFeed(null, $query);
+    echo "<h2>User Feed for: " . $userFeed->getTitle() . "</h2>";
+    echo "<ul class='user'>\n";
+    foreach ($userFeed as $entry) {
+        if ($entry instanceof Zend_Gdata_Photos_AlbumEntry) {
+            echo "\t<li class='user'>";
+            echo "<a href='?command=retrieveAlbumFeed&user=";
+            echo $userFeed->getTitle() . "&album=" . $entry->getGphotoId();
+            echo "'>";
+            $thumb = $entry->getMediaGroup()->getThumbnail();
+            echo "<img class='thumb' src='" . $thumb[0]->getUrl() . "' /><br />";
+            echo $entry->getTitle() . "</a>";
+            echo "<form action='" . getCurrentScript() . "'' method='post' class='deleteForm'>";
+            echo "<input type='hidden' name='user' value='" . $user . "' />";
+            echo "<input type='hidden' name='album' value='" . $entry->getGphotoId();
+            echo "' />";
+            echo "<input type='hidden' name='command' value='deleteAlbum' />";
+            echo "<input type='submit' value='Delete' /></form>";
+            echo "</li>\n";
+        }
+    }
+    echo "</ul><br />\n";
+
+    echo "<h3>Add an Album</h3>";
+?>
+    <form method="POST" action="<?php echo getCurrentScript(); ?>">
+        <input type="hidden" name="command" value="addAlbum" />
+        <input type="hidden" name="user" value="<?php echo $user; ?>" />
+        <input type="text" name="name" />
+        <input type="submit" name="Add Album" />
+    </form>
+<?php
+
+    displayBackLink();
+}
+
+/** 
+ * Outputs an HTML unordered list (ul), with each list item representing a
+ * photo in the user's album feed.  
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @return void
+ */
+function outputAlbumFeed($client, $user, $albumId) 
+{
+    $photos = new Zend_Gdata_Photos($client);
+    
+    $query = new Zend_Gdata_Photos_AlbumQuery();
+    $query->setUser($user);
+    $query->setAlbumId($albumId);
+
+    $albumFeed = $photos->getAlbumFeed($query);
+    echo "<h2>Album Feed for: " . $albumFeed->getTitle() . "</h2>";
+    echo "<ul class='albums'>\n";
+    foreach ($albumFeed as $entry) {
+        if ($entry instanceof Zend_Gdata_Photos_PhotoEntry) {
+            echo "\t<li class='albums'>";
+            echo "<a href='" . getCurrentScript() . "?command=retrievePhotoFeed&user=" . $user;
+            echo "&album=" . $albumId . "&photo=" . $entry->getGphotoId() . "'>";
+            $thumb = $entry->getMediaGroup()->getThumbnail();
+            echo "<img class='thumb' src='" . $thumb[1]->getUrl() . "' /><br />";
+            echo $entry->getTitle() . "</a>";
+            echo "<form action='" . getCurrentScript() . "' method='post' class='deleteForm'>";
+            echo "<input type='hidden' name='user' value='" . $user . "' />";
+            echo "<input type='hidden' name='album' value='" . $albumId . "' />";
+            echo "<input type='hidden' name='photo' value='" . $entry->getGphotoId();
+            echo "' /><input type='hidden' name='command' value='deletePhoto' />";
+            echo "<input type='submit' value='Delete' /></form>";
+            echo "</li>\n";
+        }
+    }
+    echo "</ul><br />\n";
+
+    echo "<h3>Add a Photo</h3>";
+?>
+    <form enctype="multipart/form-data" method="POST" action="<?php echo getCurrentScript(); ?>">
+        <input type="hidden" name="MAX_FILE_SIZE" value="20971520" />
+        <input type="hidden" name="command" value="addPhoto" />
+        <input type="hidden" name="user" value="<?php echo $user; ?>" />
+        <input type="hidden" name="album" value="<?php echo $albumId; ?>" />
+        Please select a photo to upload: <input name="photo" type="file" /><br />
+        <input type="submit" name="Upload" />
+    </form>
+<?php
+
+    displayBackLink();
+}
+
+/** 
+ * Outputs the feed of the specified photo
+ *
+ * @param  Zend_Http_Client $client  The authenticated client object
+ * @param  string           $user    The user's account name
+ * @param  integer          $albumId The album's id
+ * @param  integer          $photoId The photo's id
+ * @return void
+ */
+function outputPhotoFeed($client, $user, $albumId, $photoId) 
+{
+    $photos = new Zend_Gdata_Photos($client);
+
+    $query = new Zend_Gdata_Photos_PhotoQuery();
+    $query->setUser($user);
+    $query->setAlbumId($albumId);
+    $query->setPhotoId($photoId);
+    $query = $query->getQueryUrl() . "?kind=comment,tag";
+    
+    $photoFeed = $photos->getPhotoFeed($query);
+    echo "<h2>Photo Feed for: " . $photoFeed->getTitle() . "</h2>";
+    $thumbs = $photoFeed->getMediaGroup()->getThumbnail();
+    echo "<img src='" . $thumbs[2]->url . "' />";
+
+    echo "<h3 class='nopad'>Comments:</h3>";
+    echo "<ul>\n";
+    foreach ($photoFeed as $entry) {
+        if ($entry instanceof Zend_Gdata_Photos_CommentEntry) {
+            echo "\t<li>" . $entry->getContent();
+            echo "<form action='" . getCurrentScript() . "' method='post' class='deleteForm'>";
+            echo "<input type='hidden' name='user' value='" . $user . "' />";
+            echo "<input type='hidden' name='album' value='" . $albumId . "' />";
+            echo "<input type='hidden' name='photo' value='" . $photoId . "' />";
+            echo "<input type='hidden' name='comment' value='" . $entry->getGphotoId();
+            echo "' />";
+            echo "<input type='hidden' name='command' value='deleteComment' />";
+            echo "<input type='submit' value='Delete' /></form>";
+            echo "</li>\n";
+        }
+    }
+    echo "</ul>\n";
+    echo "<h4>Add a Comment</h4>";
+?>
+    <form method="POST" action="<?php echo getCurrentScript(); ?>">
+        <input type="hidden" name="command" value="addComment" />
+        <input type="hidden" name="user" value="<?php echo $user; ?>" />
+        <input type="hidden" name="album" value="<?php echo $albumId; ?>" />
+        <input type="hidden" name="photo" value="<?php echo $photoId; ?>" />
+        <input type="text" name="comment" />
+        <input type="submit" name="Comment" value="Comment" />
+    </form>
+<?php
+    echo "<br />";
+    echo "<h3 class='nopad'>Tags:</h3>";
+    echo "<ul>\n";
+    foreach ($photoFeed as $entry) {
+        if ($entry instanceof Zend_Gdata_Photos_TagEntry) {
+            echo "\t<li>" . $entry->getTitle();
+            echo "<form action='" . getCurrentScript() . "' method='post' class='deleteForm'>";
+            echo "<input type='hidden' name='user' value='" . $user . "' />";
+            echo "<input type='hidden' name='album' value='" . $albumId . "' />";
+            echo "<input type='hidden' name='photo' value='" . $photoId . "' />";
+            echo "<input type='hidden' name='tag' value='" . $entry->getContent();
+            echo "' />";
+            echo "<input type='hidden' name='command' value='deleteTag' />";
+            echo "<input type='submit' value='Delete' /></form>";
+            echo "</li>\n";
+        }
+    }
+    echo "</ul>\n";
+    echo "<h4>Add a Tag</h4>";
+?>
+    <form method="POST" action="<?php echo getCurrentScript(); ?>">
+        <input type="hidden" name="command" value="addTag" />
+        <input type="hidden" name="user" value="<?php echo $user; ?>" />
+        <input type="hidden" name="album" value="<?php echo $albumId; ?>" />
+        <input type="hidden" name="photo" value="<?php echo $photoId; ?>" />
+        <input type="text" name="tag" />
+        <input type="submit" name="Tag" value="Tag" />
+    </form>
+<?php
+
+    displayBackLink();
+}
+
+/**
+ * Output the CSS for the page
+ */
+
+?>
+<style type="text/css">
+    h2 {
+        color: #0056FF;
+    }
+    h3 {
+        color: #0056FF;
+        padding-top: 15px;
+        clear: left;
+    }
+    h3.nopad {
+        padding: 0px;
+    }
+    ul {
+        background-color: #E0EAFF;
+        color: #191D1D;
+        margin: 10px;
+        padding: 10px 10px 10px 25px;
+        border: 1px solid #515B5C;
+    }
+    ul.user, ul.albums {
+        background-color: #FFFFFF;
+        border: 0px;
+        padding: 0px;
+    }
+    li.user, li.albums {
+        display: block;
+        float: left;
+        margin: 5px;
+        padding: 5px;
+        text-align: center;
+        background-color: #E0EAFF;
+        border: 1px solid #515B5C;
+    }
+    a {
+        color: #0056FF;
+        font-weight: bold;
+        text-decoration: none;
+    }
+    a:hover {
+        text-decoration: underline;
+        color: #E00000;
+    }
+    div.menuForm {
+        margin: 10px;
+        padding: 0px 10px;
+        background-color: #E0EAFF;
+        border: 1px solid #515B5C;
+    }
+    form.deleteForm {
+        padding-left: 10px;
+        display: inline;
+    }
+    img.thumb {
+        margin: 5px;
+        border: 0px;
+    }
+</style>
+<?php
+
+/**
+ * Calls the main processing function for running in a browser
+ */
+
+processPageLoad();

+ 454 - 0
demos/Zend/Gdata/Spreadsheet-ClientLogin.php

@@ -0,0 +1,454 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata
+ */
+Zend_Loader::loadClass('Zend_Gdata');
+
+/**
+ * @see Zend_Gdata_ClientLogin
+ */
+Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
+
+/**
+ * @see Zend_Gdata_Spreadsheets
+ */
+Zend_Loader::loadClass('Zend_Gdata_Spreadsheets');
+
+/**
+ * @see Zend_Gdata_App_AuthException
+ */
+Zend_Loader::loadClass('Zend_Gdata_App_AuthException');
+
+/**
+ * @see Zend_Http_Client
+ */
+Zend_Loader::loadClass('Zend_Http_Client');
+
+
+/**
+ * SimpleCRUD
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class SimpleCRUD
+{
+    /**
+     * Constructor
+     *
+     * @param  string $email
+     * @param  string $password
+     * @return void
+     */
+    public function __construct($email, $password)
+    {
+        try {
+          $client = Zend_Gdata_ClientLogin::getHttpClient($email, $password, 
+                    Zend_Gdata_Spreadsheets::AUTH_SERVICE_NAME);
+        } catch (Zend_Gdata_App_AuthException $ae) {
+          exit("Error: ". $ae->getMessage() ."\nCredentials provided were email: [$email] and password [$password].\n");
+        }
+        
+        $this->gdClient = new Zend_Gdata_Spreadsheets($client);
+        $this->currKey = '';
+        $this->currWkshtId = '';
+        $this->listFeed = '';
+        $this->rowCount = 0;
+        $this->columnCount = 0;
+    }
+
+    /**
+     * promptForSpreadsheet
+     *
+     * @return void
+     */
+    public function promptForSpreadsheet()
+    {
+        $feed = $this->gdClient->getSpreadsheetFeed();
+        print "== Available Spreadsheets ==\n";
+        $this->printFeed($feed);
+        $input = getInput("\nSelection");
+        $currKey = split('/', $feed->entries[$input]->id->text);
+        $this->currKey = $currKey[5];
+    }
+
+    /**
+     * promptForWorksheet
+     *
+     * @return void
+     */
+    public function promptForWorksheet()
+    {
+        $query = new Zend_Gdata_Spreadsheets_DocumentQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $feed = $this->gdClient->getWorksheetFeed($query);
+        print "== Available Worksheets ==\n";
+        $this->printFeed($feed);
+        $input = getInput("\nSelection");
+        $currWkshtId = split('/', $feed->entries[$input]->id->text);
+        $this->currWkshtId = $currWkshtId[8];
+
+    }
+
+    /**
+     * promptForCellsAction
+     *
+     * @return void
+     */
+    public function promptForCellsAction()
+    {
+        echo "Pick a command:\n";
+        echo "\ndump -- dump cell information\nupdate {row} {col} {input_value} -- update cell information\n";
+        $input = getInput('Command');
+        $command = split(' ', $input);
+        if ($command[0] == 'dump') {
+            $this->cellsGetAction();
+        } else if (($command[0] == 'update') && (count($command) > 2)) {
+              $this->getRowAndColumnCount();
+                if (count($command) == 4) {
+                    $this->cellsUpdateAction($command[1], $command[2], $command[3]);
+                } elseif (count($command) > 4) {
+                    $newValue = implode(' ', array_slice($command,3));
+                    $this->cellsUpdateAction($command[1], $command[2], $newValue);
+                } else {
+                $this->cellsUpdateAction($command[1], $command[2], '');
+                }
+        } else {
+            $this->invalidCommandError($input);
+        }
+    }
+
+    /**
+     * promptToResize
+     *
+     * @param  integer $newRowCount
+     * @param  integer $newColumnCount
+     * @return boolean
+     */
+    public function promptToResize($newRowCount, $newColumnCount) {
+        $input = getInput('Would you like to resize the worksheet? [yes | no]');
+        if ($input == 'yes') {
+            return $this->resizeWorksheet($newRowCount, $newColumnCount);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * resizeWorksheet
+     *
+     * @param  integer $newRowCount
+     * @param  integer $newColumnCount
+     * @return boolean
+     */
+    public function resizeWorksheet($newRowCount, $newColumnCount) {
+        $query = new Zend_Gdata_Spreadsheets_DocumentQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $currentWorksheet = $this->gdClient->getWorksheetEntry($query);
+        $currentWorksheet = $currentWorksheet->setRowCount(new Zend_Gdata_Spreadsheets_Extension_RowCount($newRowCount));
+        $currentWorksheet = $currentWorksheet->setColumnCount(new Zend_Gdata_Spreadsheets_Extension_ColCount($newColumnCount));
+        $currentWorksheet->save();
+        $this->getRowAndColumnCount();
+        print "Worksheet has been resized to $this->rowCount rows and $this->columnCount columns.\n";
+        return true;
+    }
+
+    /**
+     * promptForListAction
+     *
+     * @return void
+     */
+    public function promptForListAction()
+    {
+        echo  "\n== Options ==\n". 
+              "dump -- dump row information\n". 
+              "insert {row_data} -- insert data in the next available cell in a given column (example: insert column_header=content)\n".
+              "update {row_index} {row_data} -- update data in the row provided (example: update row-number column-header=newdata\n". 
+              "delete {row_index} -- delete a row\n\n";
+        
+        $input = getInput('Command');
+        $command = split(' ', $input);
+        if ($command[0] == 'dump') {
+            $this->listGetAction();
+        } else if ($command[0] == 'insert') {
+            $this->listInsertAction(array_slice($command, 1));
+        } else if ($command[0] == 'update') {
+            $this->listUpdateAction($command[1], array_slice($command, 2));
+        } else if ($command[0] == 'delete') {
+            $this->listDeleteAction($command[1]);
+        } else {
+            $this->invalidCommandError($input);
+        }
+    }
+
+    /**
+     * cellsGetAction
+     *
+     * @return void
+     */
+    public function cellsGetAction()
+    {
+        $query = new Zend_Gdata_Spreadsheets_CellQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $feed = $this->gdClient->getCellFeed($query);
+        $this->printFeed($feed);
+    }
+
+    /**
+     * cellsUpdateAction
+     *
+     * @param  integer $row
+     * @param  integer $col
+     * @param  string  $inputValue
+     * @return void
+     */
+    public function cellsUpdateAction($row, $col, $inputValue)
+    {   
+        if (($row > $this->rowCount) || ($col > $this->columnCount)) {
+            print "Current worksheet only has $this->rowCount rows and $this->columnCount columns.\n";
+            if (!$this->promptToResize($row, $col)) {
+                return;
+            }
+        }
+        $entry = $this->gdClient->updateCell($row, $col, $inputValue, 
+                $this->currKey, $this->currWkshtId);
+        if ($entry instanceof Zend_Gdata_Spreadsheets_CellEntry) {
+            echo "Success!\n";
+        }
+    }
+
+    /**
+     * listGetAction
+     *
+     * @return void
+     */
+    public function listGetAction()
+    {
+        $query = new Zend_Gdata_Spreadsheets_ListQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $this->listFeed = $this->gdClient->getListFeed($query);
+        print "entry id | row-content in column A | column-header: cell-content\n". 
+              "Please note: The 'dump' command on the list feed only dumps data until the first blank row is encountered.\n\n";
+        
+        $this->printFeed($this->listFeed);
+        print "\n";
+    }
+
+    /**
+     * listInsertAction
+     *
+     * @param  mixed $rowData
+     * @return void
+     */
+    public function listInsertAction($rowData)
+    {
+        $rowArray = $this->stringToArray($rowData);
+        $entry = $this->gdClient->insertRow($rowArray, $this->currKey, $this->currWkshtId);
+        if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) {
+           foreach ($rowArray as $column_header => $value) {
+                echo "Success! Inserted '$value' in column '$column_header' at row ". substr($entry->getTitle()->getText(), 5) ."\n";
+            }
+        }
+    }
+
+    /**
+     * listUpdateAction
+     *
+     * @param  integer $index
+     * @param  mixed   $rowData
+     * @return void
+     */
+    public function listUpdateAction($index, $rowData)
+    {
+        $query = new Zend_Gdata_Spreadsheets_ListQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $this->listFeed = $this->gdClient->getListFeed($query);
+        $rowArray = $this->stringToArray($rowData);
+        $entry = $this->gdClient->updateRow($this->listFeed->entries[$index], $rowArray);
+        if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) {
+            echo "Success!\n";        $response = $entry->save();
+
+        }
+    }
+
+    /**
+     * listDeleteAction
+     *
+     * @param  integer $index
+     * @return void
+     */
+    public function listDeleteAction($index)
+    {
+        $query = new Zend_Gdata_Spreadsheets_ListQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $this->listFeed = $this->gdClient->getListFeed($query);
+        $this->gdClient->deleteRow($this->listFeed->entries[$index]);
+    }
+
+    /**
+     * stringToArray
+     *
+     * @param  string $rowData
+     * @return array
+     */
+    public function stringToArray($rowData)
+    {
+        $arr = array();
+        foreach ($rowData as $row) {
+            $temp = split('=', $row);
+            $arr[$temp[0]] = $temp[1];
+        }
+        return $arr;
+    }
+
+    /**
+     * printFeed
+     *
+     * @param  Zend_Gdata_Gbase_Feed $feed
+     * @return void
+     */
+    public function printFeed($feed)
+    {
+        $i = 0;
+        foreach($feed->entries as $entry) {
+            if ($entry instanceof Zend_Gdata_Spreadsheets_CellEntry) {
+                print $entry->title->text .' '. $entry->content->text . "\n";
+            } else if ($entry instanceof Zend_Gdata_Spreadsheets_ListEntry) {
+                print $i .' '. $entry->title->text .' | '. $entry->content->text . "\n";
+            } else {
+                print $i .' '. $entry->title->text . "\n";
+            }
+            $i++;
+        }
+    }
+
+    /**
+     * getRowAndColumnCount
+     *
+     * @return void
+     */
+    public function getRowAndColumnCount() 
+    {
+        $query = new Zend_Gdata_Spreadsheets_CellQuery();
+        $query->setSpreadsheetKey($this->currKey);
+        $query->setWorksheetId($this->currWkshtId);
+        $feed = $this->gdClient->getCellFeed($query);
+         
+        if ($feed instanceOf Zend_Gdata_Spreadsheets_CellFeed) {
+            $this->rowCount = $feed->getRowCount();
+            $this->columnCount = $feed->getColumnCount();
+        }
+    }
+
+    /**
+     * invalidCommandError
+     *
+     * @param  string $input
+     * @return void
+     */
+    public function invalidCommandError($input)
+    {
+        echo 'Invalid input: '.$input."\n";
+    }
+
+    /**
+     * promtForFeedtype
+     *
+     * @return void
+     */
+    public function promptForFeedtype() {
+
+      $input = getInput('Select to use either the cell or the list feed [cells or list]');
+
+      if ($input == 'cells') {
+        while(1) {
+          $this->promptForCellsAction();
+          }
+      } else if ($input == 'list') {
+        while(1) {
+          $this->promptForListAction();
+          }
+      } else {
+            print "Invalid input. Please try again.\n";
+            $this->promptForFeedtype();
+      }  
+    }
+
+    /**
+     * run
+     *
+     * @return void
+     */
+    public function run()
+    {
+        $this->promptForSpreadsheet();
+        $this->promptForWorksheet();
+        $this->promptForFeedtype();
+    }
+}
+
+/**
+ * getInput
+ *
+ * @param  string $text
+ * @return string
+ */
+function getInput($text)
+{
+    echo $text.': ';
+    return trim(fgets(STDIN));
+}
+
+$email = null;
+$pass = null;
+
+// process command line options
+foreach ($argv as $argument) {
+    $argParts = split('=', $argument);
+    if ($argParts[0] == '--email') {
+        $email = $argParts[1];
+    } else if ($argParts[0] == '--pass') {
+        $pass = $argParts[1];
+    }
+}
+
+if (($email == null) || ($pass == null)) {
+    $email = getInput("Please enter your email address [example: username@gmail.com]");
+    $pass = getInput("Please enter your password [example: mypassword]");
+}
+
+$sample = new SimpleCRUD($email, $pass); 
+$sample->run();

+ 44 - 0
demos/Zend/Gdata/YouTubeVideoApp/README.txt

@@ -0,0 +1,44 @@
+== YouTube data API Video App in PHP ==
+
+PHP sample code for the YouTube data API.  Utilizes the Zend Framework
+Zend_Gdata component to communicate with the YouTube data API.
+
+Requires the Zend Framework Zend_Gdata component and PHP >= 5.1.4
+This sample is run from within a web browser.  These files are required:
+
+session_details.php - a script to view log output and session variables
+operations.php - the main logic, which interfaces with the YouTube API
+index.php - the HTML to represent the web UI, contains some PHP
+video_app.css - the CSS to define the interface style
+video_app.js - the JavaScript used to provide the video list AJAX interface
+
+--------------
+
+NOTE: If using in production, some additional precautions with regards
+to filtering the input data should be used.  This code is designed only
+for demonstration purposes.
+
+--------------
+
+Please be sure to obtain a Developer Key from YouTube prior to using
+this application by visiting this site:
+
+http://code.google.com/apis/youtube/dashboard/
+        
+More information on the YouTube Data API and Tools is available here:
+        
+http://code.google.com/apis/youtube 
+
+For a video explaining the basics of how this application works, please
+visit this link:
+
+http://www.youtube.com/watch?v=iIp7OnHXBlo
+
+To see this application running live, please visit:
+
+http://googlecodesamples.com
+
+== UPDATES ==
+
+3/2009 - Removed functionality to set the Developer Key in a form. Instead,
+         it is now hard-coded in the index.php page. This reduces complexity.

+ 193 - 0
demos/Zend/Gdata/YouTubeVideoApp/index.php

@@ -0,0 +1,193 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the YouTube data API.  Utilizes the Zend Framework
+ * Zend_Gdata component to communicate with the YouTube data API.
+ *
+ * Requires the Zend Framework Zend_Gdata component and PHP >= 5.1.4
+ * This sample is run from within a web browser.  These files are required:
+ * session_details.php - a script to view log output and session variables
+ * operations.php - the main logic, which interfaces with the YouTube API
+ * index.php - the HTML to represent the web UI, contains some PHP
+ * video_app.css - the CSS to define the interface style
+ * video_app.js - the JavaScript used to provide the video list AJAX interface
+ *
+ * NOTE: If using in production, some additional precautions with regards
+ * to filtering the input data should be used.  This code is designed only
+ * for demonstration purposes.
+ */
+session_start();
+
+/**
+ * Set your developer key here. 
+ *
+ * NOTE: In a production application you may want to store this information in 
+ * an external file.
+ */
+$_SESSION['developerKey'] = '<YOUR DEVELOPER KEY>';
+
+/**
+ * Convert HTTP status into normal text.
+ *
+ * @param number $status HTTP status received after posting syndicated upload
+ * @param string $code Alphanumeric description of error
+ * @param string $videoId (optional) Video id received back to which the status
+ *        code refers to
+ */
+function uploadStatus($status, $code = null, $videoId = null)
+{
+    switch ($status) {
+        case $status < 400:
+            echo  'Success ! Entry created (id: '. $videoId .
+                  ') <a href="#" onclick=" ytVideoApp.checkUploadDetails(\''.
+                  $videoId .'\'); ">(check details)</a>';
+            break;
+        default:
+            echo 'There seems to have been an error: '. $code .
+                 '<a href="#" onclick=" ytVideoApp.checkUploadDetails(\''.
+                 $videoId . '\'); ">(check details)</a>';
+    }
+}
+
+/**
+ * Helper function to check whether a session token has been set
+ *
+ * @return boolean Returns true if a session token has been set
+ */
+function authenticated()
+{
+    if (isset($_SESSION['sessionToken'])) {
+        return true;
+    }
+}
+
+/**
+ * Helper function to print a list of authenticated actions for a user.
+ */
+function printAuthenticatedActions()
+{
+    print <<<END
+        <div id="actions"><h3>Authenticated Actions</h3>
+        <ul>
+        <li><a href="#" onclick="ytVideoApp.listVideos('search_owner', '', 1);
+        return false;">retrieve my videos</a></li>
+        <li><a href="#" onclick="ytVideoApp.prepareUploadForm();
+        return false;">upload a video</a><br />
+        <div id="syndicatedUploadDiv"></div><div id="syndicatedUploadStatusDiv">
+        </div></li>
+        <li><a href="#" onclick="ytVideoApp.retrievePlaylists();
+        return false;">manage my playlists</a><br /></li>
+        </ul></div>
+END;
+}
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+  <title>YouTube data API Video Browser in PHP</title>
+  <link href="video_app.css" type="text/css" rel="stylesheet" />
+  <script src="video_app.js" type="text/javascript"></script>
+</head>
+
+<body>
+  <div id="main">
+    <div id="titleBar">
+      <h2>YouTube data API Video App in PHP</h2>
+        <a href="session_details.php">click to examine session variables</a><br/>
+        <div id="searchBox">
+        <form id="searchForm" onsubmit="ytVideoApp.listVideos(this.queryType.value, this.searchTerm.value, 1); return false;" action="javascript:void();" >
+        <div id="searchBoxTop"><select name="queryType" onchange="ytVideoApp.queryTypeChanged(this.value, this.form.searchTerm);" >
+          <option value="search_all" selected="selected">All Videos</option>
+          <option value="search_top_rated">Top Rated Videos</option>
+          <option value="search_most_viewed">Most Viewed Videos</option>
+          <option value="search_recently_featured">Recently Featured Videos</option>
+          <option value="search_username">Videos from a specific user</option>
+          <?php
+                if (authenticated()) {
+                    echo '<option value="search_owner">Display my videos</option>';
+                }
+          ?>
+        </select></div>
+        <div><input name="searchTerm" type="text" value="YouTube Data API" />
+        <input type="submit" value="Search" /></div>
+      </form>
+    </div>
+    <br />
+
+    </div>
+    <br />
+    <!-- Authentication status -->
+    <div id="authStatus">Authentication status:
+    <?php
+      if (authenticated()) {
+          print <<<END
+              authenticated <br />
+END;
+      } else {
+          print <<<END
+                    <div id="generateAuthSubLink"><a href="#"
+                    onclick="ytVideoApp.presentAuthLink();
+                    return false;">Click here to generate authentication link</a>
+                    </div>
+END;
+    }
+    ?>
+    </div>
+    <!-- end Authentication status -->
+    <br clear="all" />
+    <?php
+        // if $_GET['status'] is populated then we have a response
+        // about a syndicated upload from YouTube's servers
+        if (isset($_GET['status'])) {
+            (isset($_GET['code']) ? $code = $_GET['code'] : $code = null);
+            (isset($_GET['id']) ? $id = $_GET['id'] : $id = null);
+            print '<div id="generalStatus">' .
+                  uploadStatus($_GET['status'], $code, $id) .
+                  '<div id="detailedUploadStatus"></div></div>';
+         }
+    ?>
+    <!-- General status -->
+    <?php
+        if (authenticated()) {
+            printAuthenticatedActions();
+        }
+    ?>
+    <!-- end General status -->
+    <br clear="all" />
+    <div id="searchResults">
+      <div id="searchResultsListColumn">
+        <div id="searchResultsVideoList"></div>
+        <div id="searchResultsNavigation">
+          <form id="navigationForm" action="javascript:void();">
+            <input type="button" id="previousPageButton" onclick="ytVideoApp.listVideos(ytVideoApp.previousQueryType, ytVideoApp.previousSearchTerm, ytVideoApp.previousPage);" value="Back" style="display: none;" />
+            <input type="button" id="nextPageButton" onclick="ytVideoApp.listVideos(ytVideoApp.previousQueryType, ytVideoApp.previousSearchTerm, ytVideoApp.nextPage);" value="Next" style="display: none;" />
+          </form>
+        </div>
+      </div>
+    <div id="searchResultsVideoColumn">
+      <div id="videoPlayer"></div>
+    </div>
+  </div>
+</div>
+</body>
+</html>

二進制
demos/Zend/Gdata/YouTubeVideoApp/notfound.jpg


+ 1097 - 0
demos/Zend/Gdata/YouTubeVideoApp/operations.php

@@ -0,0 +1,1097 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the YouTube data API.  Utilizes the Zend Framework
+ * Zend_Gdata component to communicate with the YouTube data API.
+ *
+ * Requires the Zend Framework Zend_Gdata component and PHP >= 5.1.4
+ * This sample is run from within a web browser.  These files are required:
+ * session_details.php - a script to view log output and session variables
+ * operations.php - the main logic, which interfaces with the YouTube API
+ * index.php - the HTML to represent the web UI, contains some PHP
+ * video_app.css - the CSS to define the interface style
+ * video_app.js - the JavaScript used to provide the video list AJAX interface
+ *
+ * NOTE: If using in production, some additional precautions with regards
+ * to filtering the input data should be used.  This code is designed only
+ * for demonstration purposes.
+ */
+require_once 'Zend/Loader.php';
+Zend_Loader::loadClass('Zend_Gdata_YouTube');
+Zend_Loader::loadClass('Zend_Gdata_AuthSub');
+Zend_Loader::loadClass('Zend_Gdata_App_Exception');
+
+/*
+ * The main controller logic.
+ *
+ * POST used for all authenticated requests
+ * otherwise use GET for retrieve and supplementary values
+ */
+session_start();
+setLogging('on');
+generateUrlInformation();
+
+if (!isset($_POST['operation'])) {
+    // if a GET variable is set then process the token upgrade
+    if (isset($_GET['token'])) {
+        updateAuthSubToken($_GET['token']);
+    } else {
+        if (loggingEnabled()) {
+            logMessage('reached operations.php without $_POST or $_GET variables set', 'error');
+            header('Location: index.php');
+        }
+    }
+}
+
+$operation = $_POST['operation'];
+
+switch ($operation) {
+
+    case 'create_upload_form':
+        createUploadForm($_POST['videoTitle'],
+                         $_POST['videoDescription'],
+                         $_POST['videoCategory'],
+                         $_POST['videoTags']);
+        break;
+
+    case 'edit_meta_data':
+        editVideoData($_POST['newVideoTitle'],
+                      $_POST['newVideoDescription'],
+                      $_POST['newVideoCategory'],
+                      $_POST['newVideoTags'],
+                      $_POST['videoId']);
+        break;
+
+    case 'check_upload_status':
+        checkUpload($_POST['videoId']);
+        break;
+
+    case 'delete_video':
+        deleteVideo($_POST['videoId']);
+        break;
+
+    case 'auth_sub_request':
+        generateAuthSubRequestLink();
+        break;
+
+    case 'auth_sub_token_upgrade':
+        updateAuthSubToken($_GET['token']);
+        break;
+
+    case 'clear_session_var':
+        clearSessionVar($_POST['name']);
+        break;
+
+    case 'retrieve_playlists':
+        retrievePlaylists();
+        break;
+
+    case 'create_playlist':
+        createPlaylist($_POST['playlistTitle'], $_POST['playlistDescription']);
+        break;
+
+    case 'delete_playlist':
+        deletePlaylist($_POST['playlistTitle']);
+        break;
+
+    case 'update_playlist':
+        updatePlaylist($_POST['newPlaylistTitle'],
+                       $_POST['newPlaylistDescription'],
+                       $_POST['oldPlaylistTitle']);
+        break;
+
+    case (strcmp(substr($operation, 0, 7), 'search_') == 0):
+        // initialize search specific information
+        $searchType = substr($operation, 7);
+        searchVideos($searchType, $_POST['searchTerm'], $_POST['startIndex'],
+            $_POST['maxResults']);
+        break;
+
+    case 'show_video':
+        echoVideoPlayer($_POST['videoId']);
+        break;
+
+    default:
+        unsupportedOperation($_POST);
+        break;
+}
+
+/**
+ * Perform a search on youtube. Passes the result feed to echoVideoList.
+ *
+ * @param string $searchType The type of search to perform.
+ * If set to 'owner' then attempt to authenticate.
+ * @param string $searchTerm The term to search on.
+ * @param string $startIndex Start retrieving search results from this index.
+ * @param string $maxResults The number of results to retrieve.
+ * @return void
+ */
+function searchVideos($searchType, $searchTerm, $startIndex, $maxResults)
+{
+  // create an unauthenticated service object
+    $youTubeService = new Zend_Gdata_YouTube();
+    $query = $youTubeService->newVideoQuery();
+    $query->setQuery($searchTerm);
+    $query->setStartIndex($startIndex);
+    $query->setMaxResults($maxResults);
+
+    switch ($searchType) {
+        case 'most_viewed':
+            $query->setFeedType('most viewed');
+            $query->setTime('this_week');
+            $feed = $youTubeService->getVideoFeed($query);
+            break;
+        case 'most_recent':
+            $query->setFeedType('most recent');
+            $query->setTime('this_week');
+            $feed = $youTubeService->getVideoFeed($query);
+            break;
+        case 'recently_featured':
+            $query->setFeedType('recently featured');
+            $feed = $youTubeService->getVideoFeed($query);
+            break;
+        case 'top_rated':
+            $query->setFeedType('top rated');
+            $query->setTime('this_week');
+            $feed = $youTubeService->getVideoFeed($query);
+            break;
+        case 'username':
+            $feed = $youTubeService->getUserUploads($searchTerm);
+            break;
+        case 'all':
+            $feed = $youTubeService->getVideoFeed($query);
+            break;
+        case 'owner':
+            $httpClient = getAuthSubHttpClient();
+            $youTubeService = new Zend_Gdata_YouTube($httpClient);
+            try {
+                $feed = $youTubeService->getUserUploads('default');
+                if (loggingEnabled()) {
+                    logMessage($httpClient->getLastRequest(), 'request');
+                    logMessage($httpClient->getLastResponse()->getBody(),
+                        'response');
+                }
+            } catch (Zend_Gdata_App_HttpException $httpException) {
+                print 'ERROR ' . $httpException->getMessage()
+                    . ' HTTP details<br /><textarea cols="100" rows="20">'
+                    . $httpException->getRawResponseBody()
+                    . '</textarea><br />'
+                    . '<a href="session_details.php">'
+                    . 'click here to view details of last request</a><br />';
+                return;
+            } catch (Zend_Gdata_App_Exception $e) {
+                print 'ERROR - Could not retrieve users video feed: '
+                    . $e->getMessage() . '<br />';
+                return;
+            }
+            echoVideoList($feed, true);
+            return;
+
+        default:
+            echo 'ERROR - Unknown search type - \'' . $searchType . '\'';
+            return;
+    }
+
+    if (loggingEnabled()) {
+        $httpClient = $youTubeService->getHttpClient();
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+    echoVideoList($feed);
+}
+
+/**
+ * Finds the URL for the flash representation of the specified video.
+ *
+ * @param Zend_Gdata_YouTube_VideoEntry $entry The video entry
+ * @return (string|null) The URL or null, if the URL is not found
+ */
+function findFlashUrl($entry)
+{
+    foreach ($entry->mediaGroup->content as $content) {
+        if ($content->type === 'application/x-shockwave-flash') {
+            return $content->url;
+        }
+    }
+    return null;
+}
+
+/**
+ * Check the upload status of a video
+ *
+ * @param string $videoId The video to check.
+ * @return string A message about the video's status.
+ */
+function checkUpload($videoId)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+
+    $feed = $youTubeService->getuserUploads('default');
+    $message = 'No further status information available yet.';
+
+    foreach($feed as $videoEntry) {
+        if ($videoEntry->getVideoId() == $videoId) {
+            // check if video is in draft status
+            try {
+                $control = $videoEntry->getControl();
+            } catch (Zend_Gdata_App_Exception $e) {
+                print 'ERROR - not able to retrieve control element '
+                    . $e->getMessage();
+                return;
+            }
+
+            if ($control instanceof Zend_Gdata_App_Extension_Control) {
+                if (($control->getDraft() != null) &&
+                    ($control->getDraft()->getText() == 'yes')) {
+                    $state = $videoEntry->getVideoState();
+                    if ($state instanceof Zend_Gdata_YouTube_Extension_State) {
+                        $message = 'Upload status: ' . $state->getName() . ' '
+                            . $state->getText();
+                    } else {
+                        print $message;
+                    }
+                }
+            }
+        }
+    }
+    print $message;
+}
+
+/**
+ * Store location of the demo application into session variables.
+ *
+ * @return void
+ */
+function generateUrlInformation()
+{
+    if (!isset($_SESSION['operationsUrl']) || !isset($_SESSION['homeUrl'])) {
+        $_SESSION['operationsUrl'] = 'http://'. $_SERVER['HTTP_HOST']
+                                   . $_SERVER['PHP_SELF'];
+        $path = explode('/', $_SERVER['PHP_SELF']);
+        $path[count($path)-1] = 'index.php';
+        $_SESSION['homeUrl'] = 'http://'. $_SERVER['HTTP_HOST']
+                             . implode('/', $path);
+    }
+}
+
+/**
+ * Log a message to the session variable array.
+ *
+ * @param string $message The message to log.
+ * @param string $messageType The type of message to log.
+ * @return void
+ */
+function logMessage($message, $messageType)
+{
+    if (!isset($_SESSION['log_maxLogEntries'])) {
+        $_SESSION['log_maxLogEntries'] = 20;
+    }
+
+    if (!isset($_SESSION['log_currentCounter'])) {
+        $_SESSION['log_currentCounter'] = 0;
+    }
+
+    $currentCounter = $_SESSION['log_currentCounter'];
+    $currentCounter++;
+
+    if ($currentCounter > $_SESSION['log_maxLogEntries']) {
+        $_SESSION['log_currentCounter'] = 0;
+    }
+
+    $logLocation = 'log_entry_'. $currentCounter . '_' . $messageType;
+    $_SESSION[$logLocation] = $message;
+    $_SESSION['log_currentCounter'] = $currentCounter;
+}
+
+/**
+ * Update an existing video's meta-data.
+ *
+ * @param string $newVideoTitle The new title for the video entry.
+ * @param string $newVideoDescription The new description for the video entry.
+ * @param string $newVideoCategory The new category for the video entry.
+ * @param string $newVideoTags The new set of tags for the video entry (whitespace separated).
+ * @param string $videoId The video id for the video to be edited.
+ * @return void
+ */
+function editVideoData($newVideoTitle, $newVideoDescription, $newVideoCategory, $newVideoTags, $videoId)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getVideoFeed('http://gdata.youtube.com/feeds/users/default/uploads');
+    $videoEntryToUpdate = null;
+
+    foreach($feed as $entry) {
+        if ($entry->getVideoId() == $videoId) {
+            $videoEntryToUpdate = $entry;
+            break;
+        }
+    }
+
+    if (!$videoEntryToUpdate instanceof Zend_Gdata_YouTube_VideoEntry) {
+        print 'ERROR - Could not find a video entry with id ' . $videoId
+            . '<br />' . printCacheWarning();
+        return;
+    }
+
+    try {
+        $putUrl = $videoEntryToUpdate->getEditLink()->getHref();
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not obtain video entry\'s edit link: '
+            . $e->getMessage() . '<br />';
+        return;
+    }
+
+    $videoEntryToUpdate->setVideoTitle($newVideoTitle);
+    $videoEntryToUpdate->setVideoDescription($newVideoDescription);
+    $videoEntryToUpdate->setVideoCategory($newVideoCategory);
+
+    // convert tags from space separated to comma separated
+    $videoTagsArray = explode(' ', trim($newVideoTags));
+
+    // strip out empty array elements
+    foreach($videoTagsArray as $key => $value) {
+        if (strlen($value) < 2) {
+            unset($videoTagsArray[$key]);
+        }
+    }
+
+    $videoEntryToUpdate->setVideoTags(implode(', ', $videoTagsArray));
+
+    try {
+        $updatedEntry = $youTubeService->updateEntry($videoEntryToUpdate, $putUrl);
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not post video meta-data: ' . $e->getMessage();
+        return;
+    }
+        print 'Entry updated successfully.<br /><a href="#" onclick="'
+            . 'ytVideoApp.presentFeed(\'search_owner\', 5, 0, \'none\'); '
+            . 'ytVideoApp.refreshSearchResults();" >'
+            . '(refresh your video listing)</a><br />'
+            . printCacheWarning();
+}
+
+/**
+ * Create upload form by sending the incoming video meta-data to youtube and
+ * retrieving a new entry. Prints form HTML to page.
+ *
+ * @param string $VideoTitle The title for the video entry.
+ * @param string $VideoDescription The description for the video entry.
+ * @param string $VideoCategory The category for the video entry.
+ * @param string $VideoTags The set of tags for the video entry (whitespace separated).
+ * @param string $nextUrl (optional) The URL to redirect back to after form upload has completed.
+ * @return void
+ */
+function createUploadForm($videoTitle, $videoDescription, $videoCategory, $videoTags, $nextUrl = null)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $newVideoEntry = new Zend_Gdata_YouTube_VideoEntry();
+
+    $newVideoEntry->setVideoTitle($videoTitle);
+    $newVideoEntry->setVideoDescription($videoDescription);
+
+    //make sure first character in category is capitalized
+    $videoCategory = strtoupper(substr($videoCategory, 0, 1))
+        . substr($videoCategory, 1);
+    $newVideoEntry->setVideoCategory($videoCategory);
+
+    // convert videoTags from whitespace separated into comma separated
+    $videoTagsArray = explode(' ', trim($videoTags));
+    $newVideoEntry->setVideoTags(implode(', ', $videoTagsArray));
+
+    $tokenHandlerUrl = 'http://gdata.youtube.com/action/GetUploadToken';
+    try {
+        $tokenArray = $youTubeService->getFormUploadToken($newVideoEntry, $tokenHandlerUrl);
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not retrieve token for syndicated upload. '
+            . $e->getMessage()
+            . '<br /><a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    }
+
+    $tokenValue = $tokenArray['token'];
+    $postUrl = $tokenArray['url'];
+
+    // place to redirect user after upload
+    if (!$nextUrl) {
+        $nextUrl = $_SESSION['homeUrl'];
+    }
+
+    print <<< END
+        <br /><form action="${postUrl}?nexturl=${nextUrl}"
+        method="post" enctype="multipart/form-data">
+        <input name="file" type="file"/>
+        <input name="token" type="hidden" value="${tokenValue}"/>
+        <input value="Upload Video File" type="submit" />
+        </form>
+END;
+}
+
+/**
+ * Deletes a Video.
+ *
+ * @param string $videoId Id of the video to be deleted.
+ * @return void
+ */
+function deleteVideo($videoId)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getVideoFeed('http://gdata.youtube.com/feeds/users/default/uploads');
+    $videoEntryToDelete = null;
+
+    foreach($feed as $entry) {
+        if ($entry->getVideoId() == $videoId) {
+            $videoEntryToDelete = $entry;
+            break;
+        }
+    }
+
+    // check if videoEntryToUpdate was found
+    if (!$videoEntryToDelete instanceof Zend_Gdata_YouTube_VideoEntry) {
+        print 'ERROR - Could not find a video entry with id ' . $videoId . '<br />';
+        return;
+    }
+
+    try {
+        $httpResponse = $youTubeService->delete($videoEntryToDelete);
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+         . ' HTTP details<br /><textarea cols="100" rows="20">'
+         . $httpException->getRawResponseBody()
+         . '</textarea><br />'
+         . '<a href="session_details.php">'
+         . 'click here to view details of last request</a><br />';
+        return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not delete video: '. $e->getMessage();
+        return;
+    }
+
+    print 'Entry deleted succesfully.<br />' . $httpResponse->getBody()
+        . '<br /><a href="#" onclick="'
+        . 'ytVideoApp.presentFeed(\'search_owner\', 5, 0, \'none\');"'
+        . '">(refresh your video listing)</a><br />'
+        . printCacheWarning();
+}
+
+/**
+ * Enables logging.
+ *
+ * @param string $loggingOption 'on' to turn logging on, 'off' to turn logging off.
+ * @param integer|null $maxLogItems Maximum items to log, default is 10.
+ * @return void
+ */
+function setLogging($loggingOption, $maxLogItems = 10)
+{
+    switch ($loggingOption) {
+        case 'on' :
+            $_SESSION['logging'] = 'on';
+            $_SESSION['log_currentCounter'] = 0;
+            $_SESSION['log_maxLogEntries'] = $maxLogItems;
+            break;
+
+        case 'off':
+            $_SESSION['logging'] = 'off';
+            break;
+    }
+}
+
+/**
+ * Check whether logging is enabled.
+ *
+ * @return boolean Return true if session variable for logging is set to 'on'.
+ */
+function loggingEnabled()
+{
+    if ($_SESSION['logging'] == 'on') {
+        return true;
+    }
+}
+
+/**
+ * Unset a specific session variable.
+ *
+ * @param string $name Name of the session variable to delete.
+ * @return void
+ */
+function clearSessionVar($name)
+{
+    if (isset($_SESSION[$name])) {
+        unset($_SESSION[$name]);
+    }
+    header('Location: session_details.php');
+}
+
+/**
+ * Generate an AuthSub request Link and print it to the page.
+ *
+ * @param string $nextUrl URL to redirect to after performing the authentication.
+ * @return void
+ */
+function generateAuthSubRequestLink($nextUrl = null)
+{
+    $scope = 'http://gdata.youtube.com';
+    $secure = false;
+    $session = true;
+
+    if (!$nextUrl) {
+        generateUrlInformation();
+        $nextUrl = $_SESSION['operationsUrl'];
+    }
+
+    $url = Zend_Gdata_AuthSub::getAuthSubTokenUri($nextUrl, $scope, $secure, $session);
+    echo '<a href="' . $url
+        . '"><strong>Click here to authenticate with YouTube</strong></a>';
+}
+
+/**
+ * Upgrade the single-use token to a session token.
+ *
+ * @param string $singleUseToken A valid single use token that is upgradable to a session token.
+ * @return void
+ */
+function updateAuthSubToken($singleUseToken)
+{
+    try {
+        $sessionToken = Zend_Gdata_AuthSub::getAuthSubSessionToken($singleUseToken);
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Token upgrade for ' . $singleUseToken
+            . ' failed : ' . $e->getMessage();
+        return;
+    }
+
+    $_SESSION['sessionToken'] = $sessionToken;
+    generateUrlInformation();
+    header('Location: ' . $_SESSION['homeUrl']);
+}
+
+/**
+ * Convenience method to obtain an authenticted Zend_Http_Client object.
+ *
+ * @return Zend_Http_Client An authenticated client.
+ */
+function getAuthSubHttpClient()
+{
+    try {
+        $httpClient = Zend_Gdata_AuthSub::getHttpClient($_SESSION['sessionToken']);
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not obtain authenticated Http client object. '
+            . $e->getMessage();
+        return;
+    }
+    $httpClient->setHeaders('X-GData-Key', 'key='. $_SESSION['developerKey']);
+    return $httpClient;
+}
+
+/**
+ * Echo img tags for the first thumbnail representing each video in the
+ * specified video feed. Upon clicking the thumbnails, the video should
+ * be presented.
+ *
+ * @param Zend_Gdata_YouTube_VideoFeed $feed The video feed
+ * @return void
+ */
+function echoThumbnails($feed)
+{
+    foreach ($feed as $entry) {
+        $videoId = $entry->getVideoId();
+        $firstThumbnail = htmlspecialchars(
+            $entry->mediaGroup->thumbnail[0]->url);
+        echo '<img id="' . $videoId . '" class="thumbnail" src="'
+            . $firstThumbnail .'" width="130" height="97" onclick="'
+            . 'ytVideoApp.presentVideo(\'' . $videoId . '\', 1);" '
+            . 'title="click to watch: ' .
+            htmlspecialchars($entry->getVideoTitle()) . '" />';
+     }
+}
+
+/**
+ * Echo the list of videos in the specified feed.
+ *
+ * @param Zend_Gdata_YouTube_VideoFeed $feed The video feed.
+ * @param boolean|null $authenticated If true then the videoList will
+ * attempt to create additional forms to edit video meta-data.
+ * @return void
+ */
+function echoVideoList($feed, $authenticated = false)
+{
+    $table = '<table id="videoResultList" class="videoList"><tbody>';
+    $results = 0;
+
+    foreach ($feed as $entry) {
+        $videoId = $entry->getVideoId();
+        $thumbnailUrl = 'notfound.jpg';
+        if (count($entry->mediaGroup->thumbnail) > 0) {
+            $thumbnailUrl = htmlspecialchars(
+                $entry->mediaGroup->thumbnail[0]->url);
+        }
+
+        $videoTitle = htmlspecialchars($entry->getVideoTitle());
+        $videoDescription = htmlspecialchars($entry->getVideoDescription());
+        $videoCategory = htmlspecialchars($entry->getVideoCategory());
+        $videoTags = $entry->getVideoTags();
+
+        $table .= '<tr id="video_' . $videoId . '">'
+                . '<td width="130"><img onclick="ytVideoApp.presentVideo(\''
+                . $videoId. '\')" src="' . $thumbnailUrl. '" /></td>'
+                . '<td><a href="#" onclick="ytVideoApp.presentVideo(\''
+                . $videoId . '\')">'. stripslashes($videoTitle) . '</a>'
+                . '<p class="videoDescription">'
+                . stripslashes($videoDescription) . '</p>'
+                . '<p class="videoCategory">category: ' . $videoCategory
+                . '</p><p class="videoTags">tagged: '
+                . htmlspecialchars(implode(', ', $videoTags)) . '</p>';
+
+          if ($authenticated) {
+              $table .= '<p class="edit">'
+                     . '<a onclick="ytVideoApp.presentMetaDataEditForm(\''
+                     . addslashes($videoTitle) . '\', \''
+                     . addslashes($videoDescription) . '\', \''
+                     . $videoCategory . '\', \''
+                     . addslashes(implode(', ', $videoTags)) . '\', \''
+                     . $videoId . '\');" href="#">edit video data</a> | '
+                     . '<a href="#" onclick="ytVideoApp.confirmDeletion(\''
+                     . $videoId
+                     . '\');">delete this video</a></p><br clear="all">';
+          }
+
+    $table .= '</td></tr>';
+    $results++;
+    }
+
+    if ($results < 1) {
+        echo '<br />No results found<br /><br />';
+    } else {
+        echo $table .'</tbody></table><br />';
+    }
+}
+
+/**
+ * Echo the video embed code, related videos and videos owned by the same user
+ * as the specified videoId.
+ *
+ * @param string $videoId The video
+ * @return void
+ */
+function echoVideoPlayer($videoId)
+{
+    $youTubeService = new Zend_Gdata_YouTube();
+
+    try {
+        $entry = $youTubeService->getVideoEntry($videoId);
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    }
+
+    $videoTitle = htmlspecialchars($entry->getVideoTitle());
+    $videoUrl = htmlspecialchars(findFlashUrl($entry));
+    $relatedVideoFeed = getRelatedVideos($entry->getVideoId());
+    $topRatedFeed = getTopRatedVideosByUser($entry->author[0]->name);
+
+    print <<<END
+        <b>$videoTitle</b><br />
+        <object width="425" height="350">
+        <param name="movie" value="${videoUrl}&autoplay=1"></param>
+        <param name="wmode" value="transparent"></param>
+        <embed src="${videoUrl}&autoplay=1" type="application/x-shockwave-flash" wmode="transparent"
+        width="425" height="350"></embed>
+        </object>
+END;
+
+    echo '<br />';
+    echoVideoMetadata($entry);
+    echo '<br /><b>Related:</b><br />';
+    echoThumbnails($relatedVideoFeed);
+    echo '<br /><b>Top rated videos by user:</b><br />';
+    echoThumbnails($topRatedFeed);
+}
+
+/**
+ * Returns a feed of videos related to the specified video
+ *
+ * @param string $videoId The video
+ * @return Zend_Gdata_YouTube_VideoFeed The feed of related videos
+ */
+function getRelatedVideos($videoId)
+{
+    $youTubeService = new Zend_Gdata_YouTube();
+    $ytQuery = $youTubeService->newVideoQuery();
+    // show videos related to the specified video
+    $ytQuery->setFeedType('related', $videoId);
+    // order videos by rating
+    $ytQuery->setOrderBy('rating');
+    // retrieve a maximum of 5 videos
+    $ytQuery->setMaxResults(5);
+    // retrieve only embeddable videos
+    $ytQuery->setFormat(5);
+    return $youTubeService->getVideoFeed($ytQuery);
+}
+
+/**
+ * Returns a feed of top rated videos for the specified user
+ *
+ * @param string $user The username
+ * @return Zend_Gdata_YouTube_VideoFeed The feed of top rated videos
+ */
+function getTopRatedVideosByUser($user)
+{
+    $userVideosUrl = 'http://gdata.youtube.com/feeds/users/' .
+                   $user . '/uploads';
+    $youTubeService = new Zend_Gdata_YouTube();
+    $ytQuery = $youTubeService->newVideoQuery($userVideosUrl);
+    // order by the rating of the videos
+    $ytQuery->setOrderBy('rating');
+    // retrieve a maximum of 5 videos
+    $ytQuery->setMaxResults(5);
+    // retrieve only embeddable videos
+    $ytQuery->setFormat(5);
+    return $youTubeService->getVideoFeed($ytQuery);
+}
+
+/**
+ * Echo video metadata
+ *
+ * @param Zend_Gdata_YouTube_VideoEntry $entry The video entry
+ * @return void
+ */
+function echoVideoMetadata($entry)
+{
+    $title = htmlspecialchars($entry->getVideoTitle());
+    $description = htmlspecialchars($entry->getVideoDescription());
+    $authorUsername = htmlspecialchars($entry->author[0]->name);
+    $authorUrl = 'http://www.youtube.com/profile?user=' .
+                 $authorUsername;
+    $tags = htmlspecialchars(implode(', ', $entry->getVideoTags()));
+    $duration = htmlspecialchars($entry->getVideoDuration());
+    $watchPage = htmlspecialchars($entry->getVideoWatchPageUrl());
+    $viewCount = htmlspecialchars($entry->getVideoViewCount());
+    $rating = 0;
+    if (isset($entry->rating->average)) {
+        $rating = $entry->rating->average;
+    }
+    $numRaters = 0;
+    if (isset($entry->rating->numRaters)) {
+        $numRaters = $entry->rating->numRaters;
+    }
+    $flashUrl = htmlspecialchars(findFlashUrl($entry));
+    print <<<END
+        <b>Title:</b> ${title}<br />
+        <b>Description:</b> ${description}<br />
+        <b>Author:</b> <a href="${authorUrl}">${authorUsername}</a><br />
+        <b>Tags:</b> ${tags}<br />
+        <b>Duration:</b> ${duration} seconds<br />
+        <b>View count:</b> ${viewCount}<br />
+        <b>Rating:</b> ${rating} (${numRaters} ratings)<br />
+        <b>Flash:</b> <a href="${flashUrl}">${flashUrl}</a><br />
+        <b>Watch page:</b> <a href="${watchPage}">${watchPage}</a> <br />
+END;
+}
+
+/**
+ * Print message about YouTube caching.
+ *
+ * @return string A message
+ */
+function printCacheWarning()
+{
+    return '<p class="note">'
+         . 'Please note that the change may not be reflected in the API '
+         . 'immediately due to caching.<br/>'
+         . 'Please refer to the API documentation for more details.</p>';
+}
+
+/**
+ * Retrieve playlists for the currently authenticated user and print.
+ * @return void
+ */
+function retrievePlaylists()
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getPlaylistListFeed('default');
+
+    if (loggingEnabled()) {
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+
+    if (!$feed instanceof Zend_Gdata_YouTube_PlaylistListFeed) {
+        print 'ERROR - Could not retrieve playlists<br />'.
+        printCacheWarning();
+        return;
+    }
+
+    $playlistEntries = '<ul>';
+    $entriesFound = 0;
+    foreach($feed as $entry) {
+        $playlistTitle = $entry->getTitleValue();
+        $playlistDescription = $entry->getDescription()->getText();
+        $playlistEntries .=  '<li><h3>' . $playlistTitle
+            . '</h3>' . $playlistDescription . ' | '
+            . '<a href="#" onclick="ytVideoApp.prepareUpdatePlaylistForm(\''
+            . $playlistTitle . '\', \'' . $playlistDescription
+            . '\'); ">update</a> | '
+            . '<a href="#" onclick="ytVideoApp.confirmPlaylistDeletion(\''
+            . $playlistTitle . '\');">delete</a></li>';
+        $entriesFound++;
+    }
+
+    $playlistEntries .= '</ul><br /><a href="#" '
+                        . 'onclick="ytVideoApp.prepareCreatePlaylistForm(); '
+                        . 'return false;">'
+                        . 'Add new playlist</a><br />'
+                        . '<div id="addNewPlaylist"></div>';
+
+    if (loggingEnabled()) {
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+    if ($entriesFound > 0) {
+        print $playlistEntries;
+    } else {
+        print 'No playlists found';
+    }
+}
+
+/**
+ * Create a new playlist for the currently authenticated user
+ *
+ * @param string $playlistTitle Title of the new playlist
+ * @param string $playlistDescription Description for the new playlist
+ * @return void
+ */
+function createPlaylist($playlistTitle, $playlistDescription)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getPlaylistListFeed('default');
+    if (loggingEnabled()) {
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+
+    $newPlaylist = $youTubeService->newPlaylistListEntry();
+    $newPlaylist->description = $youTubeService->newDescription()->setText($playlistDescription);
+    $newPlaylist->title = $youTubeService->newTitle()->setText($playlistDescription);
+
+    if (!$feed instanceof Zend_Gdata_YouTube_PlaylistListFeed) {
+        print 'ERROR - Could not retrieve playlists<br />'
+            . printCacheWarning();
+        return;
+    }
+
+    $playlistFeedUrl = 'http://gdata.youtube.com/feeds/users/default/playlists';
+
+    try {
+        $updatedEntry = $youTubeService->insertEntry($newPlaylist, $playlistFeedUrl);
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not create new playlist: ' . $e->getMessage();
+        return;
+    }
+
+    print 'Playlist added succesfully.<br /><a href="#" onclick="'
+        . 'ytVideoApp.retrievePlaylists();"'
+        . '">(refresh your playlist listing)</a><br />'
+        . printCacheWarning();
+}
+
+/**
+ * Delete a playlist
+ *
+ * @param string $playlistTitle Title of the playlist to be deleted
+ * @return void
+ */
+function deletePlaylist($playlistTitle)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getPlaylistListFeed('default');
+    if (loggingEnabled()) {
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+
+    $playlistEntryToDelete = null;
+
+    foreach($feed as $playlistEntry) {
+        if ($playlistEntry->getTitleValue() == $playlistTitle) {
+            $playlistEntryToDelete = $playlistEntry;
+            break;
+        }
+    }
+
+    if (!$playlistEntryToDelete instanceof Zend_Gdata_YouTube_PlaylistListEntry) {
+        print 'ERROR - Could not retrieve playlist to be deleted<br />'
+            . printCacheWarning();
+            return;
+    }
+
+    try {
+        $response = $playlistEntryToDelete->delete();
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+        return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not delete the playlist: ' . $e->getMessage();
+        return;
+    }
+
+    print 'Playlist deleted succesfully.<br />'
+        . '<a href="#" onclick="ytVideoApp.retrievePlaylists();">'
+        . '(refresh your playlist listing)</a><br />' . printCacheWarning();
+}
+
+/**
+ * Delete a playlist
+ *
+ * @param string $newplaylistTitle New title for the playlist to be updated
+ * @param string $newPlaylistDescription New description for the playlist to be updated
+ * @param string $oldPlaylistTitle Title of the playlist to be updated
+ * @return void
+ */
+function updatePlaylist($newPlaylistTitle, $newPlaylistDescription, $oldPlaylistTitle)
+{
+    $httpClient = getAuthSubHttpClient();
+    $youTubeService = new Zend_Gdata_YouTube($httpClient);
+    $feed = $youTubeService->getPlaylistListFeed('default');
+
+    if (loggingEnabled()) {
+        logMessage($httpClient->getLastRequest(), 'request');
+        logMessage($httpClient->getLastResponse()->getBody(), 'response');
+    }
+
+    $playlistEntryToDelete = null;
+
+    foreach($feed as $playlistEntry) {
+        if ($playlistEntry->getTitleValue() == $oldplaylistTitle) {
+            $playlistEntryToDelete = $playlistEntry;
+            break;
+        }
+    }
+
+    if (!$playlistEntryToDelete instanceof Zend_Gdata_YouTube_PlaylistListEntry) {
+        print 'ERROR - Could not retrieve playlist to be updated<br />'
+            . printCacheWarning();
+            return;
+    }
+
+    try {
+        $response = $playlistEntryToDelete->delete();
+        if (loggingEnabled()) {
+            logMessage($httpClient->getLastRequest(), 'request');
+            logMessage($httpClient->getLastResponse()->getBody(), 'response');
+        }
+    } catch (Zend_Gdata_App_HttpException $httpException) {
+        print 'ERROR ' . $httpException->getMessage()
+            . ' HTTP details<br /><textarea cols="100" rows="20">'
+            . $httpException->getRawResponseBody()
+            . '</textarea><br />'
+            . '<a href="session_details.php">'
+            . 'click here to view details of last request</a><br />';
+            return;
+    } catch (Zend_Gdata_App_Exception $e) {
+        print 'ERROR - Could not delete the playlist: ' . $e->getMessage();
+        return;
+    }
+
+    print 'Playlist deleted succesfully.<br /><a href="#" onclick="' .
+          'ytVideoApp.retrievePlaylists();"'.
+          '">(refresh your playlist listing)</a><br />'.
+          printCacheWarning();
+}
+
+/**
+ * Helper function if an unsupported operation is passed into this files main loop.
+ *
+ * @param array $post (Optional) The post variables that accompanied the operation, if available.
+ * @return void
+ */
+function unsupportedOperation($_POST)
+{
+    $message = 'ERROR An unsupported operation has been called - post variables received '
+             . print_r($_POST, true);
+
+    if (loggingEnabled()) {
+        logMessage($message, 'error');
+    }
+    print $message;
+}
+
+?>

+ 66 - 0
demos/Zend/Gdata/YouTubeVideoApp/session_details.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the YouTube data API.  Utilizes the Zend Framework 
+ * Zend_Gdata component to communicate with the YouTube data API.
+ *
+ * Requires the Zend Framework Zend_Gdata component and PHP >= 5.1.4
+ * This sample is run from within a web browser.  These files are required:
+ * session_details.php - a script to view log output and session variables
+ * operations.php - the main logic, which interfaces with the YouTube API
+ * index.php - the HTML to represent the web UI, contains some PHP
+ * video_app.css - the CSS to define the interface style
+ * video_app.js - the JavaScript used to provide the video list AJAX interface
+ *
+ * NOTE: If using in production, some additional precautions with regards
+ * to filtering the input data should be used.  This code is designed only
+ * for demonstration purposes.
+ */
+session_start();
+?>
+<html>
+<head>
+  <title>YouTube data API Video Browser in PHP - Session Viewer</title>
+  <link href="video_app.css" type="text/css" rel="stylesheet"/>
+  <script src="video_app.js" type="text/javascript"></script>
+</head>
+<body>
+<div id="mainSessions">
+  <div id="titleBar">
+  <div id="titleText"><h3>Session variables</h3></div><br clear="all" />
+   </div>
+<?php
+
+$session_copy = $_SESSION;
+ksort($session_copy);
+
+foreach($session_copy as $key => $value) {
+
+    print '<h3>'. $key . '</h3><div id="sessionVariable" >'. $value .'</div><br />'.
+        '<form method="POST" action="operations.php">' .
+        '<input type="hidden" value="clear_session_var" name="operation"/>'. 
+        '<input type="hidden" name="name" value="'. $key .'"/>'. 
+        '<input type="submit" value="click to delete"/></form><hr />';
+}
+?>
+<br clear="both" />
+<a href="index.php">back</a>
+    </div></body></html>

+ 236 - 0
demos/Zend/Gdata/YouTubeVideoApp/video_app.css

@@ -0,0 +1,236 @@
+body {
+  background-color: #fff;
+  color: #232323;
+  font-family: Arial, sans-serif;
+  font-size: small;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+/* TODO jhartman --> swap out with css from app engine apps
+*/
+
+
+img {
+  border: 0;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 0;
+  vertical-align: top;
+  text-align: left;
+}
+
+a:link {
+  color: #0000cc;
+}
+
+a:active {
+  color: #cc0000;
+}
+
+a:visited {
+  color: #551a8b;
+}
+
+h1 {
+  font-size: x-large;
+  margin-top: 0px;
+  margin-bottom: 5px;
+}
+
+h2 {
+  font-size: large;
+}
+
+h3 {
+  font-size: medium;
+}
+
+h4 {
+  font-size: small;
+}
+
+form {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+li {
+  margin-bottom: 0.25em;
+}
+
+pre, code {
+  color: #007000;
+  font-family: "bogus font here", monospace;
+  font-size: 100%;
+}
+
+pre {
+  border: 1px solid silver;
+  background-color: #f5f5f5;
+  padding: 0.5em;
+  overflow: auto;
+  margin: 2em;
+}
+
+pre ins {
+  color: #cc0000;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+/* forms */
+textarea {
+    width: 600px;
+    border: 1px solid #ddd;
+    padding: 5px;
+}
+
+.submit {
+    border: 1px solid #ddd;
+}
+
+input, select{
+    border: 1px solid #ddd;
+    margin-bottom: 2px;
+}
+
+hr {
+    border: none;
+    border-bottom: 1px solid #ddd;
+}
+
+/* "Selected" links */
+a.selected, .selected a, .selected {
+  color: black;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+a.selected:visited, .selected a:visited {
+  color: black;
+}
+
+p.videoDescription {
+  margin: 0;
+  padding: 0;
+  overflow: scroll;
+  font-size: small;
+}
+
+p.videoCategory {
+  margin: 0;
+  padding: 0;
+  /* overflow: scroll; */
+  font-size: x-small;
+}
+
+p.videoTags {
+  margin: 0;
+  padding: 0;
+  /* overflow: scroll; */
+  font-size: x-small;
+}
+
+p.edit {
+  font-size: small;
+}
+
+.note {
+  padding: 2px;
+  background-color: yellow;
+  color: #000;
+}
+
+#editForm {
+  font-size: small;
+}
+
+table.videoList {
+  width: 100%;
+}
+
+.videoList td {
+  padding: 10px 0px 5px 5px;
+  border-bottom: 1px solid silver;
+}
+
+#titleBar {
+  border: 1px solid silver;
+  background-color: #e5ecf9;
+  margin: 0;
+  padding: 0;
+  padding-top: 5px;
+  padding-bottom: 10px;
+  padding-left: 10px;
+  padding-right: 10px;
+  margin-top: 5px;
+  margin-bottom: 15px;
+}
+
+#titleText {
+  float: left;
+}
+
+#searchBox {
+  float: right;
+}
+
+#authStatus {
+  border-bottom: 1px solid #ddd;
+  padding: 2px;
+  margin-bottom: 10px;
+  
+}
+
+#main {
+  margin: 10px;
+}
+
+#mainSessions {
+  background-color: #ddd;
+  padding: 10px;
+}
+
+#searchResults {
+  width: 100%;
+  background-color: silver;
+}
+
+#searchResultsListColumn {
+  float: left;
+  width: 47%;
+  margin-bottom: 20px;
+  padding-right: 2px;
+}
+
+#searchResultsVideoColumn {
+  float: right;
+  width: 47%;
+  padding-left: 5px;
+  border-left: 1px solid #ddd;
+
+}
+
+#sessionVariable {
+  font-family: Courier, monospace;
+  background-color: #fff;
+  padding: 10px;
+  width: 80%;
+  overflow: scroll;
+}
+
+.thumbnail {
+  padding: 0px 0px 0px 2px;
+}
+
+#imageLoadThumbnail {
+  padding: 4px;
+  background-color: #333;
+}

+ 582 - 0
demos/Zend/Gdata/YouTubeVideoApp/video_app.js

@@ -0,0 +1,582 @@
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @fileoverview Provides functions for browsing and searching YouTube 
+ * data API feeds, as well as performing authentication, syndicated uploads
+ * and playlist management using a PHP backend powered by the Zend_Gdata component
+ * of Zend Framework.
+ */
+
+/**
+ * provides namespacing for the YouTube Video Application PHP version (ytVideoApp)
+ */
+var ytVideoApp = {};
+
+/**
+ * maximum number of results to return for list of videos
+ * @type Number
+ */
+ytVideoApp.MAX_RESULTS_LIST = 5;
+
+/**
+ * navigation button id used to page to the previous page of
+ * results in the list of videos
+ * @type String
+ */
+ytVideoApp.PREVIOUS_PAGE_BUTTON = 'previousPageButton';
+
+/**
+ * navigation button id used to page to the next page of
+ * results in the list of videos
+ * @type String
+ */
+ytVideoApp.NEXT_PAGE_BUTTON = 'nextPageButton';
+
+/**
+ * container div for navigation elements
+ * @type String
+ */
+ytVideoApp.NAVIGATION_DIV = 'navigationForm';
+
+/**
+ * container div id used to hold list of videos
+ * @type String
+ */
+ytVideoApp.VIDEO_LIST_CONTAINER_DIV = 'searchResultsVideoList';
+
+/**
+ * container div id used to hold video search results
+ * @type String
+ */
+ytVideoApp.VIDEO_SEARCH_RESULTS_DIV = 'searchResultsVideoColumn';
+
+/**
+ * container div id used to hold the video player
+ * @type String
+ */
+ytVideoApp.VIDEO_PLAYER_DIV = 'videoPlayer';
+
+/** 
+ * container div id used to hold the search box displayed at the top of
+ * the browser after one search has already been performed
+ * @type String
+ */
+ytVideoApp.TOP_SEARCH_CONTAINER_DIV = 'searchBox';
+
+/** container div to show detailed upload status
+ * @type String
+ */
+ytVideoApp.VIDEO_UPLOAD_STATUS = 'detailedUploadStatus';
+
+/** 
+ * container div to hold the form for syndicated upload
+ * @type String
+ */
+ytVideoApp.SYNDICATED_UPLOAD_DIV = 'syndicatedUploadDiv';
+
+/** 
+ * container div to hold the form to edit video meta-data
+ * @type String
+ */
+ytVideoApp.VIDEO_DATA_EDIT_DIV = 'editForm';
+
+/** 
+ * containder div to hold authentication link in special cases where auth gets
+ * set prior to developer key
+ * @type String
+ */
+ytVideoApp.AUTHSUB_REQUEST_DIV = 'generateAuthSubLink';
+
+/** 
+ * container div to hold the form for editing video meta-data
+ * @type String
+ */
+ytVideoApp.VIDEO_META_DATA_EDIT_DIV = 'editVideoMetaDataDiv';
+
+/** 
+ * container div to hold the form for adding a new playlist
+ * @type String
+ */
+ytVideoApp.PLAYLIST_ADD_DIV = 'addNewPlaylist';
+
+/**
+ * the page number to use for the next page navigation button
+ * @type Number
+ */
+ytVideoApp.nextPage = 2;
+
+/**
+ * the page number to use for the previous page navigation button
+ * @type Number
+ */
+ytVideoApp.previousPage = 0;
+
+/** 
+ * the last search term used to query - allows for the navigation
+ * buttons to know what string query to perform when clicked
+ * @type String
+ */
+ytVideoApp.previousSearchTerm = '';
+
+/**
+ * the last query type used for querying - allows for the navigation
+ * buttons to know what type of query to perform when clicked
+ * @type String
+ */
+ytVideoApp.previousQueryType = 'all';
+
+/**
+ * Retrieves a list of videos matching the provided criteria.  The list of
+ * videos can be restricted to a particular standard feed or search criteria.
+ * @param {String} op The type of action to be done.
+ *     for querying all videos, or the name of a standard feed.
+ * @param {String} searchTerm The search term(s) to use for querying as the
+ *     'vq' query parameter value
+ * @param {Number} page The 1-based page of results to return.
+ */
+ytVideoApp.listVideos = function(op, searchTerm, page) {
+  ytVideoApp.previousSearchTerm = searchTerm; 
+  ytVideoApp.previousQueryType = op; 
+  var maxResults = ytVideoApp.MAX_RESULTS_LIST;
+  var startIndex =  (((page - 1) * ytVideoApp.MAX_RESULTS_LIST) + 1);
+  ytVideoApp.presentFeed(op, maxResults, startIndex, searchTerm);
+  ytVideoApp.updateNavigation(page);
+};
+
+/**
+ * Sends an AJAX request to the server to retrieve a list of videos or
+ * the video player/metadata.  Sends the request to the specified filePath
+ * on the same host, passing the specified params, and filling the specified
+ * resultDivName with the resutls upon success.
+ * @param {String} filePath The path to which the request should be sent
+ * @param {String} params The URL encoded POST params
+ * @param {String} resultDivName The name of the DIV used to hold the results
+ */
+ytVideoApp.sendRequest = function(filePath, params, resultDivName) {
+  if (window.XMLHttpRequest) {
+    var xmlhr = new XMLHttpRequest();
+  } else {
+    var xmlhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
+  }
+
+  xmlhr.open('POST', filePath);
+  xmlhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
+
+  xmlhr.onreadystatechange = function() {
+    var resultDiv = document.getElementById(resultDivName);
+    if (xmlhr.readyState == 1) {
+      resultDiv.innerHTML = '<b>Loading...</b>'; 
+    } else if (xmlhr.readyState == 4 && xmlhr.status == 200) {
+      if (xmlhr.responseText) {
+        resultDiv.innerHTML = xmlhr.responseText;
+      }
+    } else if (xmlhr.readyState == 4) {
+      alert('Invalid response received - Status: ' + xmlhr.status);
+    }
+  }
+  xmlhr.send(params);
+}
+
+/**
+ * Uses ytVideoApp.sendRequest to display a YT video player and metadata for the
+ * specified video ID.
+ * @param {String} videoId The ID of the YouTube video to show
+ */
+ytVideoApp.presentVideo = function(videoId, updateThumbnail) {
+  var params = 'operation=show_video&videoId=' + videoId;
+  var filePath = 'operations.php';
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_PLAYER_DIV);
+}
+
+/**
+ * Creates a form to enter video meta-data in preparation for syndicated upload.
+ */
+ytVideoApp.prepareUploadForm = function() { 
+  var  metaDataForm = ['<br clear="all"><form id="uploadForm" ',
+    'onsubmit="ytVideoApp.prepareSyndicatedUpload(',
+    'this.videoTitle.value, ',
+    'this.videoDescription.value, ',
+    'this.videoCategory.value, ',
+    'this.videoTags.value); ',
+    'return false;">',
+    'Enter video title:<br /><input size="50" name="videoTitle" ',
+    'type="text" /><br />',
+    'Enter video description:<br /><textarea cols="50" ',
+    'name="videoDescription"></textarea><br />',
+    'Select a category: <select name="videoCategory">',
+    '<option value="Autos">Autos &amp; Vehicles</option>',
+    '<option value="Music">Music</option>',
+    '<option value="Animals">Pets &amp; Animals</option>',
+    '<option value="Sports">Sports</option>',
+    '<option value="Travel">Travel &amp; Events</option>',
+    '<option value="Games">Gadgets &amp; Games</option>',
+    '<option value="Comedy">Comedy</option>',
+    '<option value="People">People &amp; Blogs</option>',
+    '<option value="News">News &amp; Politics</option>',
+    '<option value="Entertainment">Entertainment</option>',
+    '<option value="Education">Education</option>',
+    '<option value="Howto">Howto &amp; Style</option>',
+    '<option value="Nonprofit">Nonprofit &amp; Activism</option>',
+    '<option value="Tech">Science &amp; Technology</option>',
+    '</select><br />',
+    'Enter some tags to describe your video ',
+    '<em>(separated by spaces)</em>:<br />',
+    '<input name="videoTags" type="text" size="50" value="video" /><br />',
+    '<input type="submit" value="go">',
+    '</form>'].join('');
+
+  document.getElementById(ytVideoApp.SYNDICATED_UPLOAD_DIV).innerHTML = metaDataForm;
+}
+
+/** 
+ * Uses ytVideoApp.sendRequest to prepare a syndicated upload.
+ * 
+ * @param {String} videoTitle The title for new video
+ * @param {String} videoDescription The video's description
+ * @param {String} videoCategory The category for the video
+ * @param {String} videoTags A white-space separated string of Tags
+ */
+ytVideoApp.prepareSyndicatedUpload = function(videoTitle, videoDescription, videoCategory, videoTags) {
+  var filePath = 'operations.php';
+  var params = 'operation=create_upload_form' +
+               '&videoTitle=' + videoTitle +
+               '&videoDescription=' + videoDescription +
+               '&videoCategory=' + videoCategory +
+               '&videoTags=' + videoTags;
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.SYNDICATED_UPLOAD_DIV);
+}
+
+/** 
+ * Uses ytVideoApp.sendRequest to create the authSub link.
+ */
+ytVideoApp.presentAuthLink = function() {
+  var filePath = 'operations.php';
+  var params = 'operation=auth_sub_request';
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.AUTHSUB_REQUEST_DIV);
+}
+
+
+/** 
+ * Uses ytVideoApp.sendRequest to check a videos upload status.
+ * 
+ * @param {String} videoId The id of the video to check
+ */
+ytVideoApp.checkUploadDetails = function(videoId) {
+  var filePath = 'operations.php';
+  var params = 'operation=check_upload_status' +
+               '&videoId=' + videoId;
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_UPLOAD_STATUS);
+}
+
+
+/** 
+ * Creates an HTML form to edit a video's meta-data, populated with the 
+ * videos current meta-data.
+ * 
+ * @param {String} oldVideoTitle The old title of the video
+ * @param {String} oldVideoDescription The old description of the video
+ * @param {String} oldVideoCategory The old category of the video
+ * @param {String} oldVideoTags The old tags for the video (separated by white-space)
+ * @param {String} videoId The id of the video to be edited
+ */
+ytVideoApp.presentMetaDataEditForm = function(oldVideoTitle, oldVideoDescription, oldVideoCategory, oldVideoTags, videoId) {
+  // split oldVideoTags by comma and present as whitespace separated
+  var oldVideoTagsArray = oldVideoTags.split(',');
+  oldVideoTags = oldVideoTagsArray.join(' ');
+  var editMetaDataForm = ['<form id="editForm" ',
+    'onsubmit="ytVideoApp.editMetaData(',
+    'this.newVideoTitle.value, ',
+    'this.newVideoDescription.value, ',
+    'this.newVideoCategory.value, ',
+    'this.newVideoTags.value, ',
+    'this.videoId.value);',
+    'return false;">',
+    'Enter a new video title:<br />',
+    '<input size="50" name="newVideoTitle" ',
+    'type="text" value="',
+    oldVideoTitle,
+    '"/><br />',
+    'Enter a new video description:<br />',
+    '<textarea cols="50" name="newVideoDescription">', 
+    oldVideoDescription,
+    '</textarea><br />',
+    'Select a new category: <select ',
+    'name="newVideoCategory">',
+    '<option value="Autos">Autos &amp; Vehicles</option>',
+    '<option value="Music">Music</option>',
+    '<option value="Animals">Pets &amp; Animals</option>',
+    '<option value="Sports">Sports</option>',
+    '<option value="Travel">Travel &amp; Events</option>',
+    '<option value="Games">Gadgets &amp; Games</option>',
+    '<option value="Comedy">Comedy</option>',
+    '<option value="People">People &amp; Blogs</option>',
+    '<option value="News">News &amp; Politics</option>',
+    '<option value="Entertainment">Entertainment</option>',
+    '<option value="Education">Education</option>',
+    '<option value="Howto">Howto &amp; Style</option>',
+    '<option value="Nonprofit">Nonprofit &amp; Activism</option>',
+    '<option value="Tech">Science &amp; Technology</option>',
+    '</select><br />',
+    'Enter some new tags to describe your video ',
+    '<em>(separated by spaces)</em>:<br />',
+    '<input name="newVideoTags" type="text" size="50" ',
+    'value="',
+    oldVideoTags,
+    '"/><br />',
+    '<input name="videoId" type="hidden" value="',
+    videoId,
+    '" /><br />',
+    '<input type="submit" value="go">',
+    '</form>'].join('');
+  
+  document.getElementById(ytVideoApp.VIDEO_SEARCH_RESULTS_DIV).innerHTML = editMetaDataForm;
+}
+
+/** 
+ * Uses ytVideoApp.sendRequest to submit updated video meta-data.
+ * 
+ * @param {String} newVideoTitle The new title of the video
+ * @param {String} newVideoDescription The new description of the video
+ * @param {String} newVideoCategory The new category of the video
+ * @param {String} newVideoTags The new tags for the video (separated by white-space)
+ * @param {String} videoId The id of the video to be edited
+ */
+ytVideoApp.editMetaData = function(newVideoTitle, newVideoDescription, newVideoCategory, newVideoTags, videoId) {
+  var filePath = 'operations.php';
+  var params = 'operation=edit_meta_data' +
+               '&newVideoTitle=' + newVideoTitle +
+               '&newVideoDescription=' + newVideoDescription +
+               '&newVideoCategory=' + newVideoCategory +
+               '&newVideoTags=' + newVideoTags +
+               '&videoId=' + videoId;
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_SEARCH_RESULTS_DIV);
+};
+
+
+/**
+ * Confirms whether user wants to delete a video.
+ * @param {String} videoId  The video Id to be deleted
+ */
+ytVideoApp.confirmDeletion = function(videoId) {
+  var answer =  confirm('Do you really want to delete the video with id: ' + videoId + ' ?');
+  if (answer) {
+    ytVideoApp.prepareDeletion(videoId);
+  }
+}
+
+/**
+ * Uses ytVideoApp.sendRequest to request a video to be deleted.
+ * @param {String} videoId  The video Id to be deleted
+ */
+ytVideoApp.prepareDeletion = function(videoId) {
+  var filePath = 'operations.php';
+  var params = 'operation=delete_video' +
+               '&videoId=' + videoId;
+
+  var table  = document.getElementById('videoResultList');
+  var indexOfRowToBeDeleted = -1;
+  var tableRows = document.getElementsByTagName('TR');
+  for (var i = 0, tableRow; tableRow = tableRows[i]; i++) {
+    if (tableRow.id == videoId) {
+      indexOfRowToBeDeleted = i;
+    }
+  }
+  if (indexOfRowToBeDeleted > -1) {
+    table.deleteRow(indexOfRowToBeDeleted);
+  }
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_SEARCH_RESULTS_DIV);
+}
+
+/**
+ * Uses ytVideoApp.sendRequest to display a list of of YT videos.
+ * @param {String} op  The operation to perform to retrieve a feed
+ * @param {Number} maxResults The maximum number of videos to list
+ * @param {Number} startIndex The first video to include in the list
+ * @param {String} searchTerm The search terms to pass to the specified feed
+ */
+ytVideoApp.presentFeed = function(op, maxResults, startIndex, searchTerm){
+  var params = 'operation=' + op + 
+               '&maxResults=' + maxResults +
+               '&startIndex=' + startIndex + 
+               '&searchTerm=' + searchTerm;
+  var filePath = 'operations.php';
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_LIST_CONTAINER_DIV);
+};
+
+/**
+ * Updates the variables used by the navigation buttons and the 'enabled' 
+ * status of the buttons based upon the current page number passed in.
+ * @param {Number} page The current page number
+ */
+ytVideoApp.updateNavigation = function(page) {
+  ytVideoApp.nextPage = page + 1;
+  ytVideoApp.previousPage = page - 1;
+  document.getElementById(ytVideoApp.NEXT_PAGE_BUTTON).style.display = 'inline';
+  document.getElementById(ytVideoApp.PREVIOUS_PAGE_BUTTON).style.display = 'inline';
+  if (ytVideoApp.previousPage < 1) {
+    document.getElementById(ytVideoApp.PREVIOUS_PAGE_BUTTON).disabled = true;
+  } else {
+    document.getElementById(ytVideoApp.PREVIOUS_PAGE_BUTTON).disabled = false;
+  }
+  document.getElementById(ytVideoApp.NEXT_PAGE_BUTTON).disabled = false;
+};
+
+/**
+ * Hides the navigation.
+ */
+ytVideoApp.hideNavigation = function() {
+  document.getElementById(ytVideoApp.NAVIGATION_DIV).style.display = 'none';
+};
+
+/**
+ * Update video results div
+ */
+ytVideoApp.refreshSearchResults = function() {
+  document.getElementById(ytVideoApp.VIDEO_SEARCH_RESULTS_DIV).innerHTML = '';
+}
+
+/**
+ * Method called when the query type has been changed.  Clears out the
+ * value of the search term input box by default if one of the standard
+ * feeds is selected.  This is to improve usability, as many of the standard
+ * feeds may not include results for even fairly popular search terms.
+ * @param {String} op The operation to perform.
+ *     for querying all videos, or the name of one of the standard feeds.
+ * @param {Node} searchTermInputElement The HTML input element for the input
+ *     element.
+ */
+ytVideoApp.queryTypeChanged = function(op, searchTermInputElement) {
+  if (op == 'search_username') {
+    searchTermInputElement.value = '-- enter username --';
+  } else if (op != 'search_all') {
+    searchTermInputElement.value = '';
+  }
+};
+
+/**
+ * Create a basic HTML form to use for creating a new playlist.
+ */
+ytVideoApp.prepareCreatePlaylistForm = function() {
+  var newPlaylistForm = ['<br /><form id="addPlaylist" ',
+    'onsubmit="ytVideoApp.createNewPlaylist(this.newPlaylistTitle.value, ',
+    'this.newPlaylistDescription.value); ">',
+    'Enter a title for the new playlist:<br />',
+    '<input size="50" name="newPlaylistTitle" type="text" /><br />',
+    'Enter a description:<br />',
+    '<textarea cols="25" name="newPlaylistDescription" >',
+    '</textarea><br />',
+    '<input type="submit" value="go">',
+    '</form>'].join('');
+    
+  document.getElementById(ytVideoApp.PLAYLIST_ADD_DIV).innerHTML = newPlaylistForm;
+}
+
+/**
+* Uses ytVideoApp.sendRequest to create a new playlist.
+*
+* @param {String} playlistTitle The title of the new playlist
+* @param {String} playlistDescription A description of the new playlist
+*/
+ytVideoApp.createNewPlaylist = function(playlistTitle, playlistDescription) {
+  var filePath = 'operations.php';
+  var params = 'operation=create_playlist' +
+               '&playlistTitle=' + playlistTitle +
+               '&playlistDescription=' + playlistDescription;
+  ytVideoApp.hideNavigation();
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_SEARCH_RESULTS_DIV);
+}
+
+/**
+ * Confirm user wants to delete a playlist
+ *
+ * @param {String} playlistTitle The title of the playlist to be deleted
+ */
+ytVideoApp.confirmPlaylistDeletion = function(playlistTitle) {
+  var answer =  confirm('Do you really want to delete the playlist titled : ' + 
+    playlistTitle + ' ?');
+  if (answer) {
+    ytVideoApp.deletePlaylist(playlistTitle);
+  }
+}
+
+/**
+* Uses ytVideoApp.sendRequest to delete a playlist.
+*
+* @param {String} playlistTitle The title of the new playlist
+*/
+ytVideoApp.deletePlaylist = function(playlistTitle) {
+  var filePath = 'operations.php';
+  var params = 'operation=delete_playlist' +
+               '&playlistTitle=' + playlistTitle;
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_SEARCH_RESULTS_DIV);
+}
+
+/**
+ * Create a basic HTML form to use for modifying a playlist.
+ *
+ * @param {String} oldPlaylistTitle The old title of the playlist
+ * @param {String} oldPlaylistDescription The old description of the playlist
+ */
+ytVideoApp.prepareUpdatePlaylistForm = function(oldPlaylistTitle, oldPlaylistDescription) {
+  var playlistUpdateForm = ['<br /><form id="updatePlaylist" ',
+    'onsubmit="ytVideoApp.updatePlaylist(this.newPlaylistTitle.value, ',
+    'this.newPlaylistDescription.value, this.oldPlaylistTitle.value);">',
+    'Enter a title for the new playlist:<br />',
+    '<input size="50" name="newPlaylistTitle" type="text" value="',
+    oldPlaylistTitle,
+    '"/><br />',
+    'Enter a description:<br />',
+    '<textarea cols="25" name="newPlaylistDescription" >',
+    oldPlaylistDescription,
+    '</textarea><br />',
+    '<input type="submit" value="go" />',
+    '<input type="hidden" value="',
+    oldPlaylistTitle,
+    '" name="oldPlaylistTitle" />',
+    '</form>'].join('');
+    
+  document.getElementById(ytVideoApp.VIDEO_SEARCH_RESULTS_DIV).innerHTML = playlistUpdateForm;
+}
+
+/**
+* Uses ytVideoApp.sendRequest to update a playlist.
+*
+* @param {String} newPlaylistTitle The new title of the playlist
+* @param {String} newPlaylistDescription A new description of the playlist
+*/
+ytVideoApp.updatePlaylist = function(newPlaylistTitle, newPlaylistDescription, oldPlaylistTitle) {
+  var filePath = 'operations.php';
+  var params = 'operation=update_playlist' +
+               '&newPlaylistTitle=' + newPlaylistTitle +
+               '&newPlaylistDescription=' + newPlaylistDescription +
+               '&oldPlaylistTitle=' + oldPlaylistTitle;
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_LIST_CONTAINER_DIV);
+}
+
+/**
+* Uses ytVideoApp.sendRequest to retrieve a users playlist.
+*
+*/
+ytVideoApp.retrievePlaylists = function() {
+  var filePath = 'operations.php';
+  var params = 'operation=retrieve_playlists';
+  ytVideoApp.hideNavigation();
+  ytVideoApp.sendRequest(filePath, params, ytVideoApp.VIDEO_LIST_CONTAINER_DIV);
+}

+ 278 - 0
demos/Zend/Gdata/YouTubeVideoBrowser/index.php

@@ -0,0 +1,278 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * PHP sample code for the YouTube data API.  Utilizes the Zend Framework 
+ * Zend_Gdata component to communicate with the YouTube data API.
+ *
+ * Requires the Zend Framework Zend_Gdata component and PHP >= 5.1.4
+ *
+ * This sample is run from within a web browser.  These files are required:
+ * index.php - the main logic, which interfaces with the YouTube API
+ * interface.html - the HTML to represent the web UI
+ * web_browser.css - the CSS to define the interface style
+ * web_browser.js - the JavaScript used to provide the video list AJAX interface
+ *
+ * NOTE: If using in production, some additional precautions with regards
+ * to filtering the input data should be used.  This code is designed only
+ * for demonstration purposes.
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * @see Zend_Gdata_YouTube
+ */
+Zend_Loader::loadClass('Zend_Gdata_YouTube');
+
+/**
+ * Finds the URL for the flash representation of the specified video
+ *
+ * @param  Zend_Gdata_YouTube_VideoEntry $entry The video entry
+ * @return string|null The URL or null, if the URL is not found
+ */
+function findFlashUrl($entry) 
+{
+    foreach ($entry->mediaGroup->content as $content) {
+        if ($content->type === 'application/x-shockwave-flash') {
+            return $content->url;
+        }
+    }
+    return null;
+}
+
+/**
+ * Returns a feed of top rated videos for the specified user
+ *
+ * @param  string $user The username 
+ * @return Zend_Gdata_YouTube_VideoFeed The feed of top rated videos
+ */
+function getTopRatedVideosByUser($user) 
+{
+    $userVideosUrl = 'http://gdata.youtube.com/feeds/users/' . 
+                     $user . '/uploads';
+    $yt = new Zend_Gdata_YouTube();
+    $ytQuery = $yt->newVideoQuery($userVideosUrl);  
+    // order by the rating of the videos
+    $ytQuery->setOrderBy('rating');
+    // retrieve a maximum of 5 videos
+    $ytQuery->setMaxResults(5);
+    // retrieve only embeddable videos
+    $ytQuery->setFormat(5);
+    return $yt->getVideoFeed($ytQuery);
+}
+
+/**
+ * Returns a feed of videos related to the specified video
+ *
+ * @param  string $videoId The video
+ * @return Zend_Gdata_YouTube_VideoFeed The feed of related videos
+ */
+function getRelatedVideos($videoId) 
+{
+    $yt = new Zend_Gdata_YouTube();
+    $ytQuery = $yt->newVideoQuery();
+    // show videos related to the specified video
+    $ytQuery->setFeedType('related', $videoId);
+    // order videos by rating
+    $ytQuery->setOrderBy('rating');
+    // retrieve a maximum of 5 videos
+    $ytQuery->setMaxResults(5);
+    // retrieve only embeddable videos
+    $ytQuery->setFormat(5);
+    return $yt->getVideoFeed($ytQuery);
+}
+
+/**
+ * Echo img tags for the first thumbnail representing each video in the 
+ * specified video feed.  Upon clicking the thumbnails, the video should
+ * be presented.
+ *
+ * @param  Zend_Gdata_YouTube_VideoFeed $feed The video feed
+ * @return void
+ */
+function echoThumbnails($feed) 
+{
+    foreach ($feed as $entry) {
+        $videoId = $entry->getVideoId();
+        echo '<img src="' . $entry->mediaGroup->thumbnail[0]->url . '" ';
+        echo 'width="80" height="72" onclick="ytvbp.presentVideo(\'' . $videoId . '\')">';
+    }
+}
+
+/**
+ * Echo the video embed code, related videos and videos owned by the same user
+ * as the specified videoId.
+ *
+ * @param  string $videoId The video
+ * @return void
+ */
+function echoVideoPlayer($videoId) 
+{
+    $yt = new Zend_Gdata_YouTube();
+    
+    $entry = $yt->getVideoEntry($videoId);
+    $videoTitle = $entry->mediaGroup->title;
+    $videoUrl = findFlashUrl($entry);
+    $relatedVideoFeed = getRelatedVideos($entry->getVideoId());
+    $topRatedFeed = getTopRatedVideosByUser($entry->author[0]->name);
+    
+    print <<<END
+    <b>$videoTitle</b><br />
+    <object width="425" height="350">
+      <param name="movie" value="${videoUrl}&autoplay=1"></param>
+      <param name="wmode" value="transparent"></param>
+      <embed src="${videoUrl}&autoplay=1" type="application/x-shockwave-flash" wmode="transparent"
+        width=425" height="350"></embed>
+    </object>
+END;
+    echo '<br />';
+    echoVideoMetadata($entry);
+    echo '<br /><b>Related:</b><br />';
+    echoThumbnails($relatedVideoFeed); 
+    echo '<br /><b>Top rated videos by user:</b><br />';
+    echoThumbnails($topRatedFeed); 
+}
+
+/**
+ * Echo video metadata
+ * 
+ * @param  Zend_Gdata_YouTube_VideoEntry $entry The video entry
+ * @return void
+ */
+function echoVideoMetadata($entry) 
+{
+    $title = $entry->mediaGroup->title;
+    $description = $entry->mediaGroup->description;
+    $authorUsername = $entry->author[0]->name;
+    $authorUrl = 'http://www.youtube.com/profile?user=' . $authorUsername;
+    $tags = $entry->mediaGroup->keywords;
+    $duration = $entry->mediaGroup->duration->seconds;
+    $watchPage = $entry->mediaGroup->player[0]->url;
+    $viewCount = $entry->statistics->viewCount;
+    $rating = $entry->rating->average;
+    $numRaters = $entry->rating->numRaters;
+    $flashUrl = findFlashUrl($entry);
+    print <<<END
+    <b>Title:</b> ${title}<br />
+    <b>Description:</b> ${description}<br />
+    <b>Author:</b> <a href="${authorUrl}">${authorUsername}</a><br />
+    <b>Tags:</b> ${tags}<br />
+    <b>Duration:</b> ${duration} seconds<br />
+    <b>View count:</b> ${viewCount}<br />
+    <b>Rating:</b> ${rating} (${numRaters} ratings)<br />
+    <b>Flash:</b> <a href="${flashUrl}">${flashUrl}</a><br />
+    <b>Watch page:</b> <a href="${watchPage}">${watchPage}</a> <br />
+END;
+}
+
+/**
+ * Echo the list of videos in the specified feed.
+ *
+ * @param  Zend_Gdata_YouTube_VideoFeed $feed The video feed
+ * @return void
+ */
+function echoVideoList($feed) 
+{
+    echo '<table class="videoList">';
+    echo '<tbody width="100%">';
+    foreach ($feed as $entry) {
+        $videoId = $entry->getVideoId();
+        $thumbnailUrl = $entry->mediaGroup->thumbnail[0]->url;
+        $videoTitle = $entry->mediaGroup->title;
+        $videoDescription = $entry->mediaGroup->description;
+        print <<<END
+        <tr onclick="ytvbp.presentVideo('${videoId}')">
+        <td width="130"><img src="${thumbnailUrl}" /></td>
+        <td width="100%">
+        <a href="#">${videoTitle}</a>
+        <p class="videoDescription">${videoDescription}</p>
+        </td>
+        </tr>
+END;
+    }
+    echo '</table>';
+}
+
+/*
+ * The main controller logic of the YouTube video browser demonstration app.
+ */
+$queryType = isset($_POST['queryType']) ? $_POST['queryType'] : null;
+
+if ($queryType === null) {
+    /* display the entire interface */
+    include 'interface.html';
+} else if ($queryType == 'show_video') {
+    /* display an individual video */
+    if (array_key_exists('videoId', $_POST)) {
+        $videoId = $_POST['videoId'];
+        echoVideoPlayer($videoId);
+    } else if (array_key_exists('videoId', $_GET)) {
+        $videoId = $_GET['videoId'];
+        echoVideoPlayer($videoId);
+    } else {
+        echo 'No videoId found.';
+        exit;
+    }
+} else {
+    /* display a list of videos */
+    $searchTerm = $_POST['searchTerm'];
+    $startIndex = $_POST['startIndex'];
+    $maxResults = $_POST['maxResults'];
+    
+    $yt = new Zend_Gdata_YouTube();
+    $query = $yt->newVideoQuery();
+    $query->setQuery($searchTerm);
+    $query->setStartIndex($startIndex);
+    $query->setMaxResults($maxResults);
+   
+    /* check for one of the standard feeds, or list from 'all' videos */ 
+    switch ($queryType) {
+    case 'most_viewed':
+        $query->setFeedType('most viewed');
+        $query->setTime('this_week');
+        $feed = $yt->getVideoFeed($query);
+        break;
+    case 'most_recent':
+        $query->setFeedType('most recent');
+        $feed = $yt->getVideoFeed($query);
+        break;
+    case 'recently_featured':
+        $query->setFeedType('recently featured');
+        $feed = $yt->getVideoFeed($query);
+        break;
+    case 'top_rated':
+        $query->setFeedType('top rated');
+        $query->setTime('this_week');
+        $feed = $yt->getVideoFeed($query);
+        break;
+    case 'all':
+        $feed = $yt->getVideoFeed($query);
+        break;
+    default:
+        echo 'ERROR - unknown queryType - "' . $queryType . '"';
+        break;
+    }
+    echoVideoList($feed);
+}

+ 79 - 0
demos/Zend/Gdata/YouTubeVideoBrowser/interface.html

@@ -0,0 +1,79 @@
+<!---
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+-->
+<html>
+<head>
+  <title>YouTube data API Video Browser in PHP</title>
+  <link href="video_browser.css" type="text/css" rel="stylesheet"/>
+  <script src="video_browser.js" type="text/javascript"></script>
+</head>
+<body>
+<div id="main">
+  <div id="titleBar">
+    <div id="titleText"><h1>YouTube data API Video Browser in PHP</h1></div>
+    <div id="searchBox" style="display: none;">
+      <form id="searchForm" onsubmit="ytvbp.listVideos(this.queryType.value, this.searchTerm.value, 1); return false;">
+        <select name="queryType" onchange="ytvbp.queryTypeChanged(this.value, this.form.searchTerm);">
+          <option value="all" selected="true">All Videos</option>
+          <option value="top_rated">Top Rated Videos</option>
+          <option value="most_viewed">Most Viewed Videos</option>
+          <option value="recently_featured">Recently Featured Videos</option>
+
+        </select>
+        <input name="searchTerm" type="text" value="puppy">
+        <input type="submit" value="Search">
+      </form>
+    </div>
+    <br />
+  </div>
+  <br clear="all" />
+  <div id="mainSearchBox">
+    <h2>Search YouTube:</h2>
+    <form id="mainSearchForm" onsubmit="ytvbp.listVideos(this.queryType.value, this.searchTerm.value, 1); document.forms.searchForm.searchTerm.value=this.searchTerm.value; ytvbp.hideMainSearch(); document.forms.searchForm.queryType.selectedIndex=this.queryType.selectedIndex; return false;">
+      <select name="queryType" onchange="ytvbp.queryTypeChanged(this.value, this.form.searchTerm);">
+        <option value="all" selected="true">All Videos</option>
+        <option value="top_rated">Top Rated Videos</option>
+        <option value="most_viewed">Most Viewed Videos</option>
+        <option value="recently_featured">Recently Featured Videos</option>
+
+      </select>
+      <input name="searchTerm" type="text" value="puppy">
+      <input type="submit" value="Search">
+    </form>
+  </div>
+  <br clear="all" />
+  <div id="searchResults">
+    <div id="searchResultsListColumn">
+      <div id="searchResultsVideoList"></div>
+      <div id="searchResultsNavigation">
+        <form id="navigationForm">
+          <input type="button" id="previousPageButton" onclick="ytvbp.listVideos(ytvbp.previousQueryType, ytvbp.previousSearchTerm, ytvbp.previousPage);" value="Back" style="display: none;"></input>
+          <input type="button" id="nextPageButton" onclick="ytvbp.listVideos(ytvbp.previousQueryType, ytvbp.previousSearchTerm, ytvbp.nextPage);" value="Next" style="display: none;"></input>
+        </form>
+      </div>
+    </div>
+    <div id="searchResultsVideoColumn">
+      <div id="videoPlayer"></div>
+    </div> 
+  </div>
+</div>
+</body>
+</html>

+ 152 - 0
demos/Zend/Gdata/YouTubeVideoBrowser/video_browser.css

@@ -0,0 +1,152 @@
+body {
+  background-color: white;
+  color: black;
+  font-family: Arial, sans-serif;
+  font-size: small;
+  margin: 8px;
+  margin-top: 3px;
+}
+
+img {
+  border: 0;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 0;
+  vertical-align: top;
+  text-align: left;
+}
+
+a:link {
+  color: #0000cc;
+}
+
+a:active {
+  color: #cc0000;
+}
+
+a:visited {
+  color: #551a8b;
+}
+
+h1 {
+  font-size: x-large;
+  margin-top: 0px;
+  margin-bottom: 5px;
+}
+
+h2 {
+  font-size: large;
+}
+
+h3 {
+  font-size: medium;
+}
+
+h4 {
+  font-size: small;
+}
+
+form {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+li {
+  margin-bottom: 0.25em;
+}
+
+pre, code {
+  color: #007000;
+  font-family: "bogus font here", monospace;
+  font-size: 100%;
+}
+
+pre {
+  border: 1px solid silver;
+  background-color: #f5f5f5;
+  padding: 0.5em;
+  overflow: auto;
+  margin: 2em;
+}
+
+pre ins {
+  color: #cc0000;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+/* "Selected" links */
+
+a.selected, .selected a, .selected {
+  color: black;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+a.selected:visited, .selected a:visited {
+  color: black;
+}
+
+p.videoDescription {
+  font-size: small;
+  margin: 0;
+  padding: 0;
+}
+
+.videoList td {
+  padding-bottom: 5px; 
+  padding-right: 5px; 
+}
+
+#titleBar {
+  border: 1px solid silver;
+  background-color: #e5ecf9;
+  font-size: large;
+  font-weight: bold;
+  margin: 0;
+  padding: 0;
+  padding-top: 5px;
+  padding-bottom: 10px;
+  padding-left: 10px;
+  padding-right: 10px;
+  margin-top: 5px;
+  margin-bottom: 15px;
+}
+
+#titleText {
+  float: left;
+}
+
+#searchBox {
+  float: right;
+}
+
+#mainSearchBox {
+  background-color: #e5ecf9;
+  border: 1px solid silver;
+  width: 250;
+  padding-top: 5px;
+  padding-bottom: 5px;
+  padding-left: 10px;
+  padding-right: 10px;
+}
+
+#searchResults {
+  width: 100%;
+}
+
+#searchResultsListColumn {
+  float: left;
+  width: 47%;
+}
+
+#searchResultsVideoColumn {
+  float: right;
+  width: 47%;
+}

+ 228 - 0
demos/Zend/Gdata/YouTubeVideoBrowser/video_browser.js

@@ -0,0 +1,228 @@
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Gdata
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @fileoverview Provides functions for browsing and searching YouTube 
+ * data API feeds using a PHP backend powered by the Zend_Gdata component
+ * of the Zend Framework.
+ */
+
+/**
+ * provides namespacing for the YouTube Video Browser PHP version (ytvbp)
+ */
+var ytvbp = {};
+
+/**
+ * maximum number of results to return for list of videos
+ * @type Number
+ */
+ytvbp.MAX_RESULTS_LIST = 5;
+
+/**
+ * navigation button id used to page to the previous page of
+ * results in the list of videos
+ * @type String
+ */
+ytvbp.PREVIOUS_PAGE_BUTTON = 'previousPageButton';
+
+/**
+ * navigation button id used to page to the next page of
+ * results in the list of videos
+ * @type String
+ */
+ytvbp.NEXT_PAGE_BUTTON = 'nextPageButton';
+
+/**
+ * container div id used to hold list of videos
+ * @type String
+ */
+ytvbp.VIDEO_LIST_CONTAINER_DIV = 'searchResultsVideoList';
+
+/**
+ * container div id used to hold the video player
+ * @type String
+ */
+ytvbp.VIDEO_PLAYER_DIV = 'videoPlayer';
+
+/**
+ * container div id used to hold the search box which displays when the page
+ * first loads
+ * @type String
+ */
+ytvbp.MAIN_SEARCH_CONTAINER_DIV = 'mainSearchBox';
+
+/** 
+ * container div id used to hold the search box displayed at the top of
+ * the browser after one search has already been performed
+ * @type String
+ */
+ytvbp.TOP_SEARCH_CONTAINER_DIV = 'searchBox';
+
+/**
+ * the page number to use for the next page navigation button
+ * @type Number
+ */
+ytvbp.nextPage = 2;
+
+/**
+ * the page number to use for the previous page navigation button
+ * @type Number
+ */
+ytvbp.previousPage = 0;
+
+/** 
+ * the last search term used to query - allows for the navigation
+ * buttons to know what string query to perform when clicked
+ * @type String
+ */
+ytvbp.previousSearchTerm = '';
+
+/**
+ * the last query type used for querying - allows for the navigation
+ * buttons to know what type of query to perform when clicked
+ * @type String
+ */
+ytvbp.previousQueryType = 'all';
+
+/**
+ * Retrieves a list of videos matching the provided criteria.  The list of
+ * videos can be restricted to a particular standard feed or search criteria.
+ * @param {String} queryType The type of query to be done - either 'all'
+ *     for querying all videos, or the name of a standard feed.
+ * @param {String} searchTerm The search term(s) to use for querying as the
+ *     'vq' query parameter value
+ * @param {Number} page The 1-based page of results to return.
+ */
+ytvbp.listVideos = function(queryType, searchTerm, page) {
+  ytvbp.previousSearchTerm = searchTerm; 
+  ytvbp.previousQueryType = queryType; 
+  var maxResults = ytvbp.MAX_RESULTS_LIST;
+  var startIndex =  (((page - 1) * ytvbp.MAX_RESULTS_LIST) + 1);
+  ytvbp.presentFeed(queryType, maxResults, startIndex, searchTerm);
+  ytvbp.updateNavigation(page);
+};
+
+/**
+ * Sends an AJAX request to the server to retrieve a list of videos or
+ * the video player/metadata.  Sends the request to the specified filePath
+ * on the same host, passing the specified params, and filling the specified
+ * resultDivName with the resutls upon success.
+ * @param {String} filePath The path to which the request should be sent
+ * @param {String} params The URL encoded POST params
+ * @param {String} resultDivName The name of the DIV used to hold the results
+ */
+ytvbp.sendRequest = function(filePath, params, resultDivName) {
+  if (window.XMLHttpRequest) {
+    var xmlhr = new XMLHttpRequest();
+  } else {
+    var xmlhr = new ActiveXObject('MSXML2.XMLHTTP.3.0');
+  }
+        
+  xmlhr.open('POST', filePath, true);
+  xmlhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
+
+  xmlhr.onreadystatechange = function() {
+    var resultDiv = document.getElementById(resultDivName);
+    if (xmlhr.readyState == 1) {
+      resultDiv.innerHTML = '<b>Loading...</b>'; 
+    } else if (xmlhr.readyState == 4 && xmlhr.status == 200) {
+      if (xmlhr.responseText) {
+        resultDiv.innerHTML = xmlhr.responseText;
+      }
+    } else if (xmlhr.readyState == 4) {
+      alert('Invalid response received - Status: ' + xmlhr.status);
+    }
+  }
+  xmlhr.send(params);
+}
+
+/**
+ * Uses ytvbp.sendRequest to display a YT video player and metadata for the
+ * specified video ID.
+ * @param {String} videoId The ID of the YouTube video to show
+ */
+ytvbp.presentVideo = function(videoId) {
+  var params = 'queryType=show_video&videoId=' + videoId;
+  var filePath = 'index.php';
+  ytvbp.sendRequest(filePath, params, ytvbp.VIDEO_PLAYER_DIV);
+}
+
+/**
+ * Uses ytvbp.sendRequest to display a list of of YT videos.
+ * @param {String} queryType The name of a standard video feed or 'all'
+ * @param {Number} maxResults The maximum number of videos to list
+ * @param {Number} startIndex The first video to include in the list
+ * @param {String} searchTerm The search terms to pass to the specified feed
+ */
+ytvbp.presentFeed = function(queryType, maxResults, startIndex, searchTerm){
+  var params = 'queryType=' + queryType + 
+               '&maxResults=' + maxResults +
+               '&startIndex=' + startIndex + 
+               '&searchTerm=' + searchTerm;
+  var filePath = 'index.php';
+  ytvbp.sendRequest(filePath, params, ytvbp.VIDEO_LIST_CONTAINER_DIV);
+}
+
+/**
+ * Updates the variables used by the navigation buttons and the 'enabled' 
+ * status of the buttons based upon the current page number passed in.
+ * @param {Number} page The current page number
+ */
+ytvbp.updateNavigation = function(page) {
+  ytvbp.nextPage = page + 1;
+  ytvbp.previousPage = page - 1;
+  document.getElementById(ytvbp.NEXT_PAGE_BUTTON).style.display = 'inline';
+  document.getElementById(ytvbp.PREVIOUS_PAGE_BUTTON).style.display = 'inline';
+  if (ytvbp.previousPage < 1) {
+    document.getElementById(ytvbp.PREVIOUS_PAGE_BUTTON).disabled = true;
+  } else {
+    document.getElementById(ytvbp.PREVIOUS_PAGE_BUTTON).disabled = false;
+  }
+  document.getElementById(ytvbp.NEXT_PAGE_BUTTON).disabled = false;
+};
+
+/**
+ * Hides the main (large) search form and enables one that's in the
+ * title bar of the application.  The main search form is only used
+ * for the first load.  Subsequent searches should use the version in
+ * the title bar.
+ */
+ytvbp.hideMainSearch = function() {
+  document.getElementById(ytvbp.MAIN_SEARCH_CONTAINER_DIV).style.display = 
+      'none';
+  document.getElementById(ytvbp.TOP_SEARCH_CONTAINER_DIV).style.display = 
+      'inline';
+};
+
+/**
+ * Method called when the query type has been changed.  Clears out the
+ * value of the search term input box by default if one of the standard
+ * feeds is selected.  This is to improve usability, as many of the standard
+ * feeds may not include results for even fairly popular search terms.
+ * @param {String} queryType The type of query being done - either 'all'
+ *     for querying all videos, or the name of one of the standard feeds.
+ * @param {Node} searchTermInputElement The HTML input element for the input
+ *     element.
+ */
+ytvbp.queryTypeChanged = function(queryType, searchTermInputElement) {
+  if (queryType != 'all') {
+    searchTermInputElement.value = '';
+  }
+};

+ 43 - 0
demos/Zend/Locale/AllLanguages.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Locale
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * This example shows how to get the language for all
+ * languages written in native letters
+ * 
+ * So en = english de = deutsch da = dánsk and so on
+ */
+
+/**
+ * @see Zend_Locale
+ */
+require_once 'Zend/Locale.php';
+
+$list = Zend_Locale::getTranslationList('language');
+
+foreach($list as $language => $content) {
+    try {
+        $lang = Zend_Locale::getTranslation($language, 'language', $language);
+        print "\n<br>[".$language."] ".$lang;
+    } catch (Exception $e) {
+        // no output
+    }
+}

+ 449 - 0
demos/Zend/Mail/SimpleMailer.php

@@ -0,0 +1,449 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Mail
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Loader
+ */
+require_once 'Zend/Loader.php';
+
+/**
+ * A simple web-mailer based on Zend_Mail_Storage classes.
+ *
+ * This simple mailer demonstrates the most important features of the mail reading classes. You can
+ * use the test mbox and maildir files or a Pop3 or Imap server. It's meant to be run in a web enviroment
+ * and CLI is not supported. Copy the files to a directory in your webroot and make sure Zend Framework
+ * is in your include path (including incubator!).
+ *
+ * SSL and TLS are supported by Zend_Mail_Storage_[Pop3|Imap], but not shown here). You'd need to add
+ *   'ssl' => 'SSL'
+ * or
+ *   'ssl' => 'TLS'
+ * if you want to use ssl support.
+ *
+ * Because of problems with Windows filenames (maildir needs : in filenames) the maildir folder is in a tar.
+ * Untar maildir.tar in maildir/ to test maildir support (won't work on Windows).
+ *
+ * The structure of the class is very simple. Every method named show...() output HTML, run() inits mail storage
+ * after login and calls a show method, everything else inits and checks variables and mail storage handler.
+ *
+ * @category   Zend
+ * @package    Zend_Mail
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Demo_Zend_Mail_SimpleMailer
+{
+    /**
+     * Mail storage type (mbox, mbox-folder, maildir, maildir-folder, pop3, imap)
+     *
+     * @var string
+     */
+    private $type;
+
+    /**
+     * Filename, dirname or hostname for current mailstorage
+     *
+     * @var string
+     */
+    private $param;
+
+    /**
+     * Selected mail message or null if none
+     *
+     * @var integer
+     */
+    private $messageNum;
+
+    /**
+     * Mail storage handler
+     *
+     * @var Zend_Mail_Storage
+     */
+    private $mail;
+
+    /**
+     * Query string with current selection for output
+     *
+     * @var string
+     */
+    private $queryString;
+
+   /**
+     * Don't run run(), needed for auth
+     *
+     * @var boolean
+     */
+    private $noRun = false;
+
+    /**
+     * Init class for run() and output
+     *
+     * @return void
+     */
+    function __construct()
+    {
+        $this->initVars();
+        $this->loadClasses();
+        $this->whitelistParam();
+
+        // we use http auth for username and password or mail storage
+        if (($this->type == 'pop3' || $this->type == 'imap') && !isset($_SERVER['PHP_AUTH_USER'])) {
+            $this->needAuth();
+            return;
+        }
+
+        switch ($this->type) {
+            case 'mbox':
+                $this->mail = new Zend_Mail_Storage_Mbox(array('filename' => $this->param));
+                break;
+            case 'mbox-folder':
+                $this->mail = new Zend_Mail_Storage_Folder_Mbox(array('dirname' => $this->param));
+                break;
+            case 'maildir':
+                $this->mail = new Zend_Mail_Storage_Maildir(array('dirname' => $this->param));
+                break;
+            case 'maildir-folder':
+                $this->mail = new Zend_Mail_Storage_Folder_Maildir(array('dirname' => $this->param));
+                break;
+            case 'pop3':
+                $this->mail = new Zend_Mail_Storage_Pop3(array('host'     => $this->param,
+                                                               'user'     => $_SERVER['PHP_AUTH_USER'],
+                                                               'password' => $_SERVER['PHP_AUTH_PW']));
+                break;
+            case 'imap':
+                $this->mail = new Zend_Mail_Storage_Imap(array('host'     => $this->param,
+                                                               'user'     => $_SERVER['PHP_AUTH_USER'],
+                                                               'password' => $_SERVER['PHP_AUTH_PW']));
+                break;
+            default:
+                $this->mail = null;
+                break;
+        }
+    }
+
+    /**
+     * Check parameter and type
+     *
+     * @return void
+     */
+    function whitelistParam()
+    {
+        $whitelist = array('mbox'           => array('mbox/INBOX', 'mbox/subfolder/test'),
+                           'mbox-folder'    => array('mbox'),
+                           'maildir'        => array('maildir', 'maildir/.subfolder', 'maildir/.subfolder.test'),
+                           'maildir-folder' => array('maildir', 'maildir/.subfolder', 'maildir/.subfolder.test'),
+                           'pop3'           => array(),
+                           'imap'           => array());
+
+        if ($this->type === null || @$whitelist[$this->type] === array() || @in_array($this->param, $whitelist[$this->type])) {
+            return;
+        }
+
+        throw new Exception('Unknown type or param not in whitelist');
+    }
+
+    /**
+     * Load needed classes
+     *
+     * @return void
+     */
+    function loadClasses()
+    {
+        $classname = array('mbox'           => 'Zend_Mail_Storage_Mbox',
+                           'mbox-folder'    => 'Zend_Mail_Storage_Folder_Mbox',
+                           'maildir'        => 'Zend_Mail_Storage_Maildir',
+                           'maildir-folder' => 'Zend_Mail_Storage_Folder_Maildir',
+                           'pop3'           => 'Zend_Mail_Storage_Pop3',
+                           'imap'           => 'Zend_Mail_Storage_Imap');
+
+        if (isset($classname[$this->type])) {
+            Zend_Loader::loadClass($classname[$this->type]);
+        }
+
+        Zend_Loader::loadClass('Zend_Mail_Storage');
+    }
+
+    /**
+     * Init variables
+     *
+     * @return void
+     */
+    function initVars()
+    {
+        $this->type        = isset($_GET['type'])   ? $_GET['type']   : null;
+        $this->param       = isset($_GET['param'])  ? $_GET['param']  : null;
+        $this->folder      = isset($_GET['folder']) ? $_GET['folder'] : null;
+        $this->messageNum  = isset($_GET['message']) && is_numeric($_GET['message']) ? $_GET['message'] : null;
+        $this->queryString = http_build_query(array('type'   => $this->type,
+                                                    'param'  => $this->param,
+                                                    'folder' => $this->folder));
+    }
+
+    /**
+     * Send http auth headers, for username and password in pop3 and imap
+     *
+     * @return void
+     */
+    function needAuth()
+    {
+        header("WWW-Authenticate: Basic realm='{$this->type} credentials'");
+        header('HTTP/1.0 401 Please enter credentials');
+        $this->noRun = true;
+    }
+
+    /**
+     * Get data from mail storage and output html
+     *
+     * @return void
+     */
+    function run()
+    {
+        if ($this->noRun) {
+            return;
+        }
+
+        if ($this->mail instanceof Zend_Mail_Storage_Folder_Interface && $this->folder) {
+            // could also be done in constructor of $this->mail with parameter 'folder' => '...'
+            $this->mail->selectFolder($this->folder);
+        }
+
+        $message = null;
+        try {
+            if ($this->messageNum) {
+                $message = $this->mail->getMessage($this->messageNum);
+            }
+        } catch(Zend_Mail_Exception $e) {
+            // ignored, $message is still null and we display the list
+        }
+
+        if (!$this->mail) {
+            $this->showChooseType();
+        } else if ($message) {
+            $this->showMessage($message);
+        } else {
+            $this->showList();
+        }
+    }
+
+    /**
+     * Output html header
+     *
+     * @param  string $title page title
+     * @return void
+     */
+    function showHeader($title)
+    {
+        echo "<html><head>
+              <title>{$title}</title>
+              <style>
+              table {border: 1px solid black; border-collapse: collapse}
+              td, th {border: 1px solid black; padding: 3px; text-align: left}
+              th {text-align: right; background: #eee}
+              tr.unread td {font-weight: bold}
+              tr.flagged td {font-style: italic}
+              tr.new td {color: #800}
+              .message {white-space: pre; font-family: monospace; padding: 0.5em}
+              dl dt {font-style: italic; padding: 1em 0; border-top: 1px #888 dashed}
+              dl dd {padding-bottom: 1em}
+              dl dt:first-child {border: none; padding-top: 0}
+              </style>
+              </head><body><h1>{$title}</h1>";
+    }
+
+    /**
+     * Output html footer
+     *
+     * @return void
+     */
+    function showFooter()
+    {
+        echo '</body></html>';
+    }
+
+    /**
+     * Output type selection AKA "login-form"
+     *
+     * @return void
+     */
+    function showChooseType()
+    {
+        $this->showHeader('Choose Type');
+
+        echo '<form><label>Mbox file</label><input name="param" value="mbox/INBOX"/>
+              <input type="hidden" name="type" value="mbox"/><input type="submit"/></form>
+
+              <form><label>Mbox folder</label><input name="param" value="mbox"/>
+              <input type="hidden" name="type" value="mbox-folder"/><input type="submit"/></form>
+
+              <form><label>Maildir file</label><input name="param" value="maildir"/>
+              <input type="hidden" name="type" value="maildir"/><input type="submit"/></form>
+
+              <form><label>Maildir folder</label><input name="param" value="maildir"/>
+              <input type="hidden" name="type" value="maildir-folder"/><input type="submit"/></form>
+
+              <form><label>Pop3 Host</label><input name="param" value="localhost"/>
+              <input type="hidden" name="type" value="pop3"/><input type="submit"/></form>
+
+              <form><label>IMAP Host</label><input name="param" value="localhost"/>
+              <input type="hidden" name="type" value="imap"/><input type="submit"/></form>';
+
+        $this->showFooter();
+    }
+
+    /**
+     * Output mail message
+     *
+     * @return void
+     */
+    function showMessage($message)
+    {
+        try {
+            $from = $message->from;
+        } catch(Zend_Mail_Exception $e) {
+            $from = '(unknown)';
+        }
+
+        try {
+            $to = $message->to;
+        } catch(Zend_Mail_Exception $e) {
+            $to = '(unknown)';
+        }
+
+        try {
+            $subject = $message->subject;
+        } catch(Zend_Mail_Exception $e) {
+            $subject = '(unknown)';
+        }
+
+        $this->showHeader($subject);
+
+        echo "<table>
+              <tr><th>From:</td><td>$from</td></tr>
+              <tr><th>Subject:</td><td>$subject</td></tr>
+              <tr><th>To:</td><td>$to</td></tr><tr><td colspan='2' class='message'>";
+
+        if ($message->isMultipart()) {
+            echo '<dl>';
+            foreach (new RecursiveIteratorIterator($message) as $part) {
+                echo "<dt>Part with type {$part->contentType}:</dt><dd>";
+                echo htmlentities($part);
+                echo '</dd>';
+            }
+            echo '</dl>';
+        } else {
+            echo htmlentities($message->getContent());
+        }
+
+        echo "</td></tr></table><a href='?{$this->queryString}'>back to list</a>";
+
+        if ($this->messageNum > 1) {
+            echo " - <a href=\"?{$this->queryString}&message=", $this->messageNum - 1, '">prev</a>';
+        }
+
+        if ($this->messageNum < $this->mail->countMessages()) {
+            echo " - <a href=\"?{$this->queryString}&message=", $this->messageNum + 1, '">next</a>';
+        }
+
+        $this->showFooter();
+    }
+
+    /**
+     * Output message list
+     *
+     * @return void
+     */
+    function showList()
+    {
+        $this->showHeader('Overview');
+
+        echo '<table><tr><td></td><th>From</th><th>To</th><th>Subject</th></tr>';
+
+        foreach ($this->mail as $num => $message) {
+            if ($this->mail->hasFlags) {
+                $class = array();
+
+                if ($message->hasFlag(Zend_Mail_Storage::FLAG_RECENT)) {
+                    $class['unread'] = 'unread';
+                    $class['new']    = 'new';
+                }
+                if (!$message->hasFlag(Zend_Mail_Storage::FLAG_SEEN)) {
+                    $class['unread'] = 'unread';
+                }
+                if ($message->hasFlag(Zend_Mail_Storage::FLAG_FLAGGED)) {
+                    $class['flagged'] = 'flagged';
+                }
+
+                $class = implode(' ', $class);
+                echo "<tr class='$class'>";
+            } else {
+                echo '<tr>';
+            }
+
+            echo "<td><a href='?{$this->queryString}&message=$num'>read</a></td>";
+
+            try {
+                echo "<td>{$message->from}</td><td>{$message->to}</td><td>{$message->subject}</td>";
+            } catch(Zend_Mail_Exception $e){
+                echo '<td><em>error</em></td>';
+            }
+
+            echo '</tr>';
+        }
+
+        echo '</table>';
+
+        if ($this->mail instanceof Zend_Mail_Storage_Folder_Interface) {
+            $this->showFolders();
+        }
+
+        $this->showFooter();
+    }
+
+    /**
+     * Output folder list
+     *
+     * @return void
+     */
+    function showFolders()
+    {
+        echo "<br><form method='get' action='?{$this->queryString}'><label>Change folder:</label>
+              <select name='folder'>";
+
+        $folders = new RecursiveIteratorIterator($this->mail->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
+
+        foreach ($folders as $localName => $folder) {
+            echo '<option ';
+            if (!$folder->isSelectable()) {
+                echo 'disabled="disabled" ';
+            }
+            $localName = str_pad('', $folders->getDepth() * 12, '&nbsp;', STR_PAD_LEFT) . $localName;
+            echo "value='$folder'>$localName</option>";
+        }
+
+        echo "</select><input type='submit' value='change'><input type='hidden' name='param' value='{$this->param}'>
+              <input type='hidden' name='type' value='{$this->type}'></form>";
+    }
+}
+
+// init and run mailer
+$SimpleMailer = new Demo_Zend_Mail_SimpleMailer();
+$SimpleMailer->run();

二進制
demos/Zend/Mail/maildir/maildir.tar


+ 100 - 0
demos/Zend/Mail/mbox/INBOX

@@ -0,0 +1,100 @@
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+Return-Path: <from@example.com>
+Delivered-To: to@example.com
+Received: by example.com
+	id 1; Sun, 30 Apr 2006 19:00:00 +0200 (CEST)
+Received: by localhost
+	id 1; Sun, 30 Apr 2006 19:10:00 +0200 (CEST)
+To: to@example.com
+Subject: Simple Message
+Message-Id: <20060430185000.1@example.com>
+Date: Sun, 30 Apr 2006 18:50:00 +0200 (CEST)
+From: from@example.com
+
+This is a simple test message
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+To: bar@example.com
+Subject: A Really Simple Message
+From: foo@example.com
+
+Message
+
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+To: river@example.com
+Subject: To the River
+Date: Sun, 01 Jan 1829 00:00:00 +0000
+From: poe@example.com
+Message-Id: <18290101000000.0000@example.com>
+Content-type: text/plain
+MIME-version: 1.0
+X-Twin: the good
+X-Twin: the evil
+
+Fair river! in thy bright, clear flow 
+Of crystal, wandering water,
+Thou art an emblem of the glow 
+Of beautythe unhidden heart
+The playful maziness of art
+In old Alberto's daughter; 
+
+But when within thy wave she looks
+Which glistens then, and trembles
+Why, then, the prettiest of brooks 
+Her worshipper resembles;
+For in his heart, as in thy stream, 
+Her image deeply lies
+His heart which trembles at the beam 
+Of her soul-searching eyes.
+
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+To: foo@example.com
+Subject: multipart
+Date: Sun, 01 Jan 2000 00:00:00 +0000
+From: crazy@example.com
+Content-type: multipart/alternative; boundary="crazy-multipart"
+MIME-version: 1.0
+
+multipart message
+--crazy-multipart
+Content-type: text/plain
+
+The first part 
+is horizontal
+
+--crazy-multipart
+Content-type: text/x-vertical
+
+T s p i v
+h e a s e
+e c r   r
+  o t   t
+  n     i
+  d     c
+        a
+        l
+--crazy-multipart--
+
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+To: foo@example.com
+Subject: multipart
+Date: Sun, 01 Jan 2000 01:00:00 +0000
+From: normal@example.com
+Content-type: multipart/alternative; boundary="normal-multipart"
+MIME-version: 1.0
+
+multipart message
+--normal-multipart
+Content-type: text/html
+
+<html>
+<head><style>
+body{background:#008;color:white}
+em{color:#f88}
+</style></head>
+<body>Again a <em>simple</em> message</body>
+</html>
+--normal-multipart
+Content-type: text/plain
+
+Again a simple message
+--normal-multipart--

+ 14 - 0
demos/Zend/Mail/mbox/subfolder/test

@@ -0,0 +1,14 @@
+From next-message@example.com  Mon Jan 00 00:00:00 0000
+Return-Path: <from@example.com>
+Delivered-To: to@example.com
+Received: by example.com
+	id 1; Sun, 30 May 2006 19:00:00 +0200 (CEST)
+Received: by localhost
+	id 1; Sun, 30 May 2006 19:10:00 +0200 (CEST)
+To: to@example.com
+Subject: Message in subfolder
+Message-Id: <20060530185000.1@example.com>
+Date: Sun, 30 May 2006 18:50:00 +0200 (CEST)
+From: from@example.com
+
+This is the message in the subfolder

二進制
demos/Zend/OpenId/login-bg.gif


+ 64 - 0
demos/Zend/OpenId/mvc_auth/application/controllers/ErrorController.php

@@ -0,0 +1,64 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Controller_Action
+ */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * Error Controller 
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @uses       Zend_Controller_Action
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ErrorController extends Zend_Controller_Action
+{
+    /**
+     * Handle errors
+     * 
+     * @return void
+     */
+    public function errorAction()
+    {
+        $errors = $this->_getParam('error_handler', false);
+        if (!$errors) {
+            // Unknown application error
+            return $this->render('500');
+        }
+
+        switch ($errors->type) {
+            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
+            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
+                // Page not found (404) error
+                $this->render('404');
+                break;
+            default:
+                // Application (500) error
+                $this->render('500');
+                break;
+        }
+    }
+}

+ 112 - 0
demos/Zend/OpenId/mvc_auth/application/controllers/IndexController.php

@@ -0,0 +1,112 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Controller_Action
+ */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * @see Zend_Auth
+ */
+require_once 'Zend/Auth.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @uses       Zend_Controller_Action
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class IndexController extends Zend_Controller_Action
+{
+    /**
+     * indexAction
+     *
+     * @return void
+     */
+    public function indexAction() 
+    {
+        $auth = Zend_Auth::getInstance();
+        if (!$auth->hasIdentity()) {
+            $this->_redirect('/index/login');
+        } else {
+            $this->_redirect('/index/welcome');
+        }
+    }
+
+    /**
+     * welcomeAction
+     *
+     * @return void
+     */
+    public function welcomeAction() 
+    {
+        $auth = Zend_Auth::getInstance();
+        if (!$auth->hasIdentity()) {
+            $this->_redirect('index/login');
+        }
+        $this->view->user = $auth->getIdentity();
+    }
+
+    /**
+     * loginAction
+     *
+     * @return void
+     */
+    public function loginAction()
+    {
+        $this->view->status = "";
+        if (($this->_request->isPost() &&
+             $this->_request->getPost('openid_action') == 'login' &&
+             $this->_request->getPost('openid_identifier', '') !== '') ||
+            ($this->_request->isPost() &&
+             $this->_request->getPost('openid_mode') !== null) ||
+            (!$this->_request->isPost() &&
+             $this->_request->getQuery('openid_mode') != null)) {
+            Zend_Loader::loadClass('Zend_Auth_Adapter_OpenId');
+            $auth = Zend_Auth::getInstance();
+            $result = $auth->authenticate(
+                new Zend_Auth_Adapter_OpenId($this->_request->getPost('openid_identifier')));
+            if ($result->isValid()) {
+                $this->_redirect('/index/welcome');
+            } else {
+                $auth->clearIdentity();
+                foreach ($result->getMessages() as $message) {
+                    $this->view->status .= "$message<br>\n";
+                }
+            }
+        }
+        $this->render();
+    }
+
+    /**
+     * logoutAction
+     *
+     * @return void
+     */
+    public function logoutAction()
+    {
+        Zend_Auth::getInstance()->clearIdentity();
+        $this->_redirect('/index/index');
+    }
+}

+ 12 - 0
demos/Zend/OpenId/mvc_auth/application/views/scripts/error/404.phtml

@@ -0,0 +1,12 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <title>ZF OpenId Auth Application</title>
+</head>
+<body>
+Page not found!
+</body>
+</html>

+ 12 - 0
demos/Zend/OpenId/mvc_auth/application/views/scripts/error/500.phtml

@@ -0,0 +1,12 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <title>ZF OpenId Auth Application</title>
+</head>
+<body>
+An error occurred in this application!
+</body>
+</html>

+ 17 - 0
demos/Zend/OpenId/mvc_auth/application/views/scripts/index/login.phtml

@@ -0,0 +1,17 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <title>ZF OpenId Auth Application</title>
+</head>
+<body>
+<?php echo $this->status; ?>
+<form method="post"><fieldset>
+<legend>OpenID Login</legend>
+<input type="text" name="openid_identifier" value="">
+<input type="submit" name="openid_action" value="login">
+</fieldset></form>
+</body>
+</html>

+ 14 - 0
demos/Zend/OpenId/mvc_auth/application/views/scripts/index/welcome.phtml

@@ -0,0 +1,14 @@
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <title>ZF OpenId Auth Application</title>
+</head>
+<body>
+Hello <?php echo $this->user; ?>!
+<br />
+<a href="logout">logout</a>
+</body>
+</html>

+ 2 - 0
demos/Zend/OpenId/mvc_auth/html/.htaccess

@@ -0,0 +1,2 @@
+RewriteEngine on
+RewriteRule !\.(js|ico|gif|jpg|png|css|php)$ index.php

+ 1 - 0
demos/Zend/OpenId/mvc_auth/html/config.ini

@@ -0,0 +1 @@
+baseUrl=/demos/Zend/OpenId/mvc_auth/html

+ 36 - 0
demos/Zend/OpenId/mvc_auth/html/index.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Controller_Front
+ */
+require_once 'Zend/Controller/Front.php';
+
+/**
+ * @see Zend_Config_Ini
+ */
+require_once 'Zend/Config/Ini.php';
+
+$config = new Zend_Config_Ini(dirname(__FILE__)."/config.ini");
+$front = Zend_Controller_Front::getInstance();
+$front->setControllerDirectory(dirname(dirname(__FILE__)) . '/application/controllers')
+      ->setBaseUrl($config->baseUrl);
+$front->dispatch();

+ 9 - 0
demos/Zend/OpenId/templates/identity.phtml

@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+<link rel="openid.server" href="<?php echo $this->server;?>" />
+</head>
+<body>
+<?php echo $this->server . '?openid=' . $this->name;?>
+</body>
+</html>

+ 10 - 0
demos/Zend/OpenId/templates/identity2.phtml

@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+<link rel="openid2.provider" href="<?php echo $this->server;?>" />
+<link rel="openid2.local_id" href="<?php echo $this->server . '?openid=' . $this->name;?>" />
+</head>
+<body>
+<?php echo $this->server . '?openid2=' . $this->name;?>
+</body>
+</html>

+ 83 - 0
demos/Zend/OpenId/templates/login.phtml

@@ -0,0 +1,83 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+<style>
+input.openid_login {
+    background: url(login-bg.gif) no-repeat;
+    background-color: #fff;
+    background-position: 0 50%;
+    color: #000;
+    padding-left: 18px;
+    width: 220px;
+    margin-right: 10px;
+}
+input.openid_password {
+    background-color: #fff;
+    color: #000;
+    padding-left: 18px;
+    width: 220px;
+    margin-right: 10px;
+}
+</style>
+<script language="JavaScript">
+function init() {
+<?php
+    if (isset($this->error)) {
+        echo '    alert("' . $this->error . '");' . "\n";
+    }
+?>
+    if (document.forms['login'].elements['openid_url'].value == '') {
+        document.forms['login'].elements['openid_url'].focus();
+    } else {
+        document.forms['login'].elements['openid_password'].focus();
+    }
+}
+</script>
+</head>
+<body onLoad="init()">
+<?php
+if (isset($this->error)) {
+    echo '<noscript><div><b><font color="#ff0000">Error: ' . $this->error . '</font></b></div></noscript>';
+}
+?>
+<div>
+<form name="login" action="<?php echo $_SERVER['REQUEST_URI']; ?>"
+    method="post" onsubmit="this.login.disabled=true;">
+<fieldset id="openid">
+<legend>OpenID Login</legend>
+<input type="hidden" name="openid_action" value="login">
+<table border="0"><tr><td>
+<label>OpenID URL:</label>
+</td><td>
+<input type="text" class="openid_login"
+<?php
+    if (isset($this->id)) {
+        echo ' value="' . $this->id . '"';
+    }
+    if (isset($this->ro)) {
+        echo ' readonly="1" disabled="1">'."\n";
+        echo '<input type="hidden" name="openid_url" value="' . $this->id . '"';
+    } else {
+        echo ' name="openid_url"';
+    }
+?>>
+</td></tr><tr><td>
+<label>Password:</label>
+</td><td>
+<input type="password" name="openid_password" class="openid_password">
+</td><td></tr><tr><td>&nbsp;</td><td>
+<input type="submit" name="login" value="login">
+</td></tr></table>
+<?php
+    if (!isset($this->ro)) {
+        echo '<br>';
+        echo '<a href="?openid.action=register">register</a>';
+    }
+
+?>
+</fieldset>
+</form>
+</div>
+</body>
+</html>

+ 14 - 0
demos/Zend/OpenId/templates/profile.phtml

@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+</head>
+<body>
+<p>Your are logged in as <a href="<?php echo $this->url;?>"><?php echo $this->url;?></a></p>
+<br>
+<a href="<?php echo $_SERVER['PHP_SELF'];?>?openid_action=logout">LogOut</a>
+<h3>Trusted Sites:</h3>
+<table border="0">
+<?php echo $this->sites;?>
+</table>
+</html>

+ 69 - 0
demos/Zend/OpenId/templates/register.phtml

@@ -0,0 +1,69 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+<style>
+input.openid_login {
+    background: url(login-bg.gif) no-repeat;
+    background-color: #fff;
+    background-position: 0 50%;
+    color: #000;
+    padding-left: 18px;
+    width: 220px;
+    margin-right: 10px;
+}
+input.openid_password {
+    background-color: #fff;
+    color: #000;
+    padding-left: 18px;
+    width: 220px;
+    margin-right: 10px;
+}
+</style>
+<script language="JavaScript">
+function init() {
+<?php
+    if (isset($this->error)) {
+        echo '    alert("' . $this->error . '");' . "\n";
+    }
+?>
+    if (document.forms['register'].elements['openid_name'].value == '') {
+        document.forms['register'].elements['openid_name'].focus();
+    } else {
+        document.forms['register'].elements['openid_password'].focus();
+    }
+}
+</script>
+</head>
+<body onLoad="init()">
+<?php
+if (isset($this->error)) {
+    echo '<noscript><div><b><font color="#ff0000">Error: ' . $this->error . '</font></b></div></noscript>';
+}
+?>
+<div>
+<form name="register" action="<?php $_SERVER['PHP_SELF']; ?>"
+    method="post" onsubmit="this.login.disabled=true;">
+<fieldset id="openid">
+<legend>Register OpenID Account</legend>
+<input type="hidden" name="openid_action" value="register">
+<table border="0"><tr><td>
+<label>Name:</label>
+</td><td>
+<input type="text" name="openid_name" <?php if (isset($this->name)) {echo ' value="' . $this->name . '"';} ?>>
+</td></tr><tr><td>
+<label>Password:</label>
+</td><td>
+<input type="password" name="openid_password">
+</td></tr><tr><td>
+<label>Re-type:</label>
+</td><td>
+<input type="password" name="openid_password2">
+</td><td></tr><tr><td>&nbsp;</td><td>
+<input type="submit" name="login" value="login">
+</td></tr></table>
+</fieldset>
+</form>
+</div>
+</body>
+</html>

+ 11 - 0
demos/Zend/OpenId/templates/registration_complete.phtml

@@ -0,0 +1,11 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+</head>
+<body>
+<p>Than you for registration!</p>
+<p>Your OpenID identity <b><a href="<?php echo $this->url;?>"><?php echo $this->url;?></a></b></p>
+<p>You also can use OpenID 2.0 identity <b><a href="<?php echo $this->url2;?>"><?php echo $this->url2;?></a></b></p>
+</body>
+</html>

+ 18 - 0
demos/Zend/OpenId/templates/trust.phtml

@@ -0,0 +1,18 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Server Example</title>
+</head>
+<body>
+<p>A site identifying as <a href="<?php echo $this->site;?>"><?php echo $this->site;?></a> has asked us for confirmation that <a href="<?php echo $this->url;?>"><?php echo $this->url;?></a> is your identity URL.</p>
+<form name="trust" action="<?php echo $_SERVER['REQUEST_URI']; ?>" method="post">
+<?php echo $this->sreg;?>
+<input type="checkbox" name="forever">
+<label for="forever">forever</label><br>
+<input type="hidden" name="openid_action" value="trust">
+<br>
+<input type="submit" name="allow" value="Allow">
+<input type="submit" name="deny" value="Deny">
+</form>
+</body>
+</html>

+ 68 - 0
demos/Zend/OpenId/test_auth.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+$dir = realpath(dirname(__FILE__)."/../../..");
+set_include_path("$dir/incubator/library" . PATH_SEPARATOR . "$dir/library" . PATH_SEPARATOR . get_include_path());
+
+/**
+ * @see Zend_Auth
+ */
+require_once "Zend/Auth.php";
+
+/**
+ * @see Zend_Auth_Adapter_OpenId
+ */
+require_once "Zend/Auth/Adapter/OpenId.php";
+
+$status = "";
+$auth = Zend_Auth::getInstance();
+if ((isset($_POST['openid_action']) &&
+     $_POST['openid_action'] == "login" &&
+     !empty($_POST['openid_identifier'])) ||
+    isset($_GET['openid_mode']) ||
+    isset($_POST['openid_mode'])) {
+    $result = $auth->authenticate(
+    new Zend_Auth_Adapter_OpenId(@$_POST['openid_identifier']));
+    if ($result->isValid()) {
+        Zend_OpenId::redirect(Zend_OpenId::selfURL());
+    } else {
+        $auth->clearIdentity();
+        foreach ($result->getMessages() as $message) {
+            $status .= "$message<br>\n";
+        }
+    }
+} else if ($auth->hasIdentity()) {
+    if (isset($_POST['openid_action']) &&
+        $_POST['openid_action'] == "logout") {
+        $auth->clearIdentity();
+    } else {
+        $status = "You are logged-in as " . $auth->getIdentity() . "<br>\n";
+    }
+}
+?>
+<html><body>
+<?php echo "$status";?>
+<form method="post"><fieldset>
+<legend>OpenID Login</legend>
+<input type="text" name="openid_identifier" value="">
+<input type="submit" name="openid_action" value="login">
+<input type="submit" name="openid_action" value="logout">
+</fieldset></form></body></html>

+ 128 - 0
demos/Zend/OpenId/test_consumer.php

@@ -0,0 +1,128 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+$dir = realpath(dirname(__FILE__)."/../../..");
+set_include_path("$dir/library" . PATH_SEPARATOR . get_include_path());
+
+/**
+ * @see Zend_OpenId_Consumer
+ */
+require_once "Zend/OpenId/Consumer.php";
+
+/**
+ * @see Zend_OpenId_Extension_Sreg
+ */
+require_once "Zend/OpenId/Extension/Sreg.php";
+
+$id = "";
+$status = "";
+$data = array();
+if (isset($_POST['openid_action']) &&
+    $_POST['openid_action'] == "login" &&
+    !empty($_POST['openid_identifier'])) {
+
+    $consumer = new Zend_OpenId_Consumer();
+    $props = array();
+    foreach (Zend_OpenId_Extension_Sreg::getSregProperties() as $prop) {
+        if (isset($_POST[$prop])) {
+            if ($_POST[$prop] === "required") {
+                $props[$prop] = true;
+            } else if ($_POST[$prop] === "optional") {
+                $props[$prop] = false;
+            }
+        }
+    }
+    $sreg = new Zend_OpenId_Extension_Sreg($props, null, 1.1);
+    $id = $_POST['openid_identifier'];
+    if (!$consumer->login($id, null, null, $sreg)) {
+        $status = "OpenID login failed (".$consumer->getError().")";
+    }
+} else if (isset($_GET['openid_mode'])) {
+    if ($_GET['openid_mode'] == "id_res") {
+        $sreg = new Zend_OpenId_Extension_Sreg();
+        $consumer = new Zend_OpenId_Consumer();
+        if ($consumer->verify($_GET, $id, $sreg)) {
+            $status = "VALID $id";
+            $data = $sreg->getProperties();
+        } else {
+            $status = "INVALID $id (".$consumer->getError().")";
+        }
+    } else if ($_GET['openid_mode'] == "cancel") {
+        $status = "CANCELED";
+    }
+}
+$sreg_html = "";
+$sreg = new Zend_OpenId_Extension_Sreg();
+foreach (Zend_OpenId_Extension_Sreg::getSregProperties() as $prop) {
+    $val = isset($data[$prop]) ? $data[$prop] : "";
+    $sreg_html .= <<<EOF
+<tr><td>$prop</td>
+<td>
+  <input type="radio" name="$prop" value="required">
+</td><td>
+  <input type="radio" name="$prop" value="optional">
+</td><td>
+  <input type="radio" name="$prop" value="none" checked="1">
+</td><td>
+  $val
+</td></tr>
+EOF;
+}
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<title>Zend OpenID Consumer Example</title>
+<style>
+input.openid_login {
+    background: url(login-bg.gif) no-repeat;
+    background-color: #fff;
+    background-position: 0 50%;
+    color: #000;
+    padding-left: 18px;
+    width: 220px;
+    margin-right: 10px;
+}
+</style>
+</head>
+<body>
+<?php echo "$status<br>\n";?>
+<div>
+<form action="<?php echo Zend_OpenId::selfUrl(); ?>"
+    method="post" onsubmit="this.login.disabled=true;">
+<fieldset id="openid">
+<legend>OpenID Login</legend>
+<input type="hidden" name="openid_action" value="login">
+<div>
+<input type="text" name="openid_identifier" class="openid_login" value="<?php echo $id;?>">
+<input type="submit" name="login" value="login">
+<table border="0" cellpadding="2" cellspacing="2">
+<tr><td>&nbsp;</td><td>requird</td><td>optional</td><td>none</td><td>&nbsp</td></tr>
+<?php echo "$sreg_html<br>\n";?>
+</table>
+<br>
+<a href="<?php echo dirname(Zend_OpenId::selfUrl()); ?>/test_server.php?openid.action=register">register</a>
+</div>
+</fieldset>
+</form>
+</div>
+</body>
+</html>

+ 268 - 0
demos/Zend/OpenId/test_server.php

@@ -0,0 +1,268 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_OpenId
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+$dir = realpath(dirname(__FILE__)."/../../..");
+set_include_path("$dir/library" . PATH_SEPARATOR . get_include_path());
+
+/**
+ * @see Zend_OpenId_Provider
+ */
+require_once "Zend/OpenId/Provider.php";
+
+/**
+ * @see Zend_OpenId_Extension_Sreg
+ */
+require_once "Zend/OpenId/Extension/Sreg.php";
+
+/**
+ * @see Zend_Session_Namespace
+ */
+require_once "Zend/Session/Namespace.php";
+
+$server = new Zend_OpenId_Provider();
+
+/**
+ * trust_form
+ *
+ * @param  string $site
+ * @param  array|boolean $trusted
+ * @return string
+ */
+function trust_form($site, $trusted) {
+    if (is_array($trusted)) {
+        $str = "";
+        if (isset($trusted['Zend_OpenId_Extension_Sreg'])) {
+            $trusted = $trusted['Zend_OpenId_Extension_Sreg'];
+            foreach ($trusted as $key => $val) {
+                $str .= "$key:\"$val\";";
+            }
+        }
+        $trusted = true;
+    }
+    $s = '<form method="POST">'
+       . '<tr><td>'
+       . '<input type="hidden" name="openid_action" value="trust">'
+       . '<input type="hidden" name="site" value="' . $site . '">'
+       . $site
+//       . '</td><td>'
+//     . ($trusted ? 'allowed' : 'denied')
+       . '</td><td>'
+       . ($trusted ?
+          '<input type="submit" style="width:100px" name="deny" value="Deny">' :
+          '<input type="submit" style="width:100px" name="allow" value="Allow">')
+       . '</td><td>'
+       .  '<input type="submit" style="width:100px" name="del" value="Del">'
+       . '</td><td>'.$str.'</td></tr>'
+       . '</form>';
+    return $s;
+}
+
+/**
+ * sreg_form
+ *
+ * @param  Zend_OpenId_Extension_Sreg $sreg
+ * @return string
+ */
+function sreg_form(Zend_OpenId_Extension_Sreg $sreg)
+{
+    $s = "";
+    $props = $sreg->getProperties();
+    if (is_array($props) && count($props) > 0) {
+        $s = 'It also requests additinal information about you';
+        $s .= ' (fields marked by <u>*</u> are required)<br>';
+        $s .= '<table border="0" cellspacing="2" cellpadding="2">';
+        foreach ($props as $prop => $val) {
+            if ($val) {
+                $s .= '<tr><td><u>'.$prop.':*</u></td>';
+            } else {
+                $s .= '<tr><td>'.$prop.':</u></td>';
+            }
+            $value = "";
+            $s .= '<td><input type="text" name="openid.sreg.'.$prop.'" value="'.$value.'"></td></tr>';
+        }
+        $s .= '</table><br>';
+        $policy = $sreg->getPolicyUrl();
+        if (!empty($policy)) {
+            $s .= 'The private policy can be found at <a href="'.$policy.'">'.$policy.'</a>.<br>';
+        }
+    }
+    return $s;
+}
+
+$session = new Zend_Session_Namespace("opeinid.server");
+Zend_Session::start();
+
+$ret = false;
+if ($_SERVER["REQUEST_METHOD"] == "GET") {
+    if (!isset($_GET['openid_action']) && isset($_GET['openid_mode'])) {
+        $ret = $server->handle($_GET, new Zend_OpenId_Extension_Sreg());
+    } else {
+        require_once 'Zend/View.php';
+
+        $view = new Zend_View();
+        $view->setScriptPath(dirname(__FILE__) . '/templates');
+        $view->strictVars(true);
+
+        if (isset($session->id)) {
+            $view->id = $session->id;
+        }
+        if (isset($session->error)) {
+            $view->error = $session->error;
+            unset($session->error);
+        }
+        if (isset($_GET['openid_action'])) {
+            if ($_GET['openid_action'] == 'register') {
+                $ret = $view->render('register.phtml');
+            } else if ($_GET['openid_action'] == 'registration_complete' &&
+                       isset($_GET['openid_name'])) {
+                $view->name = $_GET['openid_name'];
+                $view->url = Zend_OpenId::selfURL() . '?openid=' . $view->name;
+                if ($server->hasUser($view->url)) {
+                    $view->url2 = Zend_OpenId::selfURL() . '?openid2=' . $view->name;
+                    $ret = $view->render('registration_complete.phtml');
+                }
+            } else if ($_GET['openid_action'] == 'logout') {
+                $server->logout();
+                header('Location: ' . $_SERVER['PHP_SELF']);
+                exit;
+            } else if ($_GET['openid_action'] == 'login') {
+                if (isset($_GET['openid_identity'])) {
+                    $view->id = $_GET['openid_identity'];
+                    $view->ro = true;
+                }
+                $ret = $view->render('login.phtml');
+            } else if ($_GET['openid_action'] == 'trust') {
+                if ($server->getLoggedInUser() !== false) {
+                    $view->site = $server->getSiteRoot($_GET);
+                    $view->url = $server->getLoggedInUser();
+                    $sreg = new Zend_OpenId_Extension_Sreg();
+                    $sreg->parseRequest($_GET);
+                    $view->sreg = sreg_form($sreg);
+                    if ($server->hasUser($view->url)) {
+                        $ret = $view->render('trust.phtml');
+                    }
+                }
+            }
+        } else if (isset($_GET['openid'])) {
+            $url = Zend_OpenId::selfURL() . '?openid=' . $_GET['openid'];
+            if ($server->hasUser($url)) {
+                $view->server = Zend_OpenId::selfURL();
+                $view->name = $_GET['openid'];
+                $ret = $view->render('identity.phtml');
+            }
+        } else if (isset($_GET['openid2'])) {
+            $url = Zend_OpenId::selfURL() . '?openid=' . $_GET['openid2'];
+            if ($server->hasUser($url)) {
+                $view->server = Zend_OpenId::selfURL();
+                $view->name = $_GET['openid2'];
+                $ret = $view->render('identity2.phtml');
+            }
+        } else {
+            if ($server->getLoggedInUser() !== false) {
+                $view->url = $server->getLoggedInUser();
+                if ($server->hasUser($view->url)) {
+                    $sites = $server->getTrustedSites();
+                    $s = "";
+                    foreach ($sites as $site => $trusted) {
+                        if (is_bool($trusted) || is_array($trusted)) {
+                            $s .= trust_form($site, $trusted);
+                        }
+                    }
+                    if (empty($s)) {
+                        $s = "<tr><td>None</td></tr>";
+                    }
+                    $view->sites = $s;
+                    $ret = $view->render('profile.phtml');
+                }
+            } else {
+                $ret = $view->render('login.phtml');
+            }
+        }
+    }
+} else if ($_SERVER["REQUEST_METHOD"] == "POST") {
+    if (!isset($_POST['openid_action']) && isset($_POST['openid_mode'])) {
+        $ret = $server->handle($_POST, new Zend_OpenId_Extension_Sreg());
+    } else if (isset($_POST['openid_action'])) {
+        if ($_POST['openid_action'] == 'login' &&
+            isset($_POST['openid_url']) &&
+            isset($_POST['openid_password'])) {
+            if (!$server->login($_POST['openid_url'],
+                                $_POST['openid_password'])) {
+                $session->error = 'Wrong identity/password!';
+                $session->id = $_POST['openid_url'];
+            }
+            unset($_GET['openid_action']);
+            Zend_OpenId::redirect($_SERVER['PHP_SELF'], $_GET);
+        } else if ($_POST['openid_action'] == 'register' &&
+                  isset($_POST['openid_name']) &&
+                  isset($_POST['openid_password']) &&
+                  isset($_POST['openid_password2'])) {
+
+            $url = Zend_OpenId::selfURL() . '?openid=' . $_POST['openid_name'];
+            if ($_POST['openid_password'] != $_POST['openid_password2']) {
+                $session->name = $_POST['openid_name'];
+                $session->error = 'Password mismatch.';
+                header('Location: ' . $_SERVER['PHP_SELF'] . '?openid.action=register');
+            } else if ($server->register($url, $_POST['openid_password'])) {
+                header('Location: ' . $_SERVER['PHP_SELF'] . '?openid.action=registration_complete&openid.name=' . $_POST['openid_name']);
+            } else {
+                $session->error = 'Registration failed. Try another name.';
+                header('Location: ' . $_SERVER['PHP_SELF'] . '?openid.action=register');
+            }
+            exit;
+        } else if ($_POST['openid_action'] == 'trust') {
+            if (isset($_GET['openid_return_to'])) {
+                $sreg = new Zend_OpenId_Extension_Sreg();
+                $sreg->parseResponse($_POST);
+                if (isset($_POST['allow'])) {
+                    if (isset($_POST['forever'])) {
+                        $server->allowSite($server->getSiteRoot($_GET), $sreg);
+                    }
+                    unset($_GET['openid_action']);
+                    $server->respondToConsumer($_GET, $sreg);
+                } else if (isset($_POST['deny'])) {
+                    if (isset($_POST['forever'])) {
+                        $server->denySite($server->getSiteRoot($_GET));
+                    }
+                    Zend_OpenId::redirect($_GET['openid_return_to'], array('openid.mode'=>'cancel'));
+                }
+            } else if (isset($_POST['allow'])) {
+                $server->allowSite($_POST['site']);
+                header('Location: ' . $_SERVER['PHP_SELF']);
+                exit;
+            } else if (isset($_POST['deny'])) {
+                $server->denySite($_POST['site']);
+                header('Location: ' . $_SERVER['PHP_SELF']);
+                exit;
+            } else if (isset($_POST['del'])) {
+                $server->delSite($_POST['site']);
+                header('Location: ' . $_SERVER['PHP_SELF']);
+                exit;
+            }
+        }
+    }
+}
+if (is_string($ret)) {
+    echo $ret;
+} else if ($ret !== true) {
+    header('HTTP/1.0 403 Forbidden');
+    echo 'Forbidden';
+}

+ 262 - 0
demos/Zend/Pdf/demo.php

@@ -0,0 +1,262 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Pdf
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+// set include_path to library/ directory only -- see ticket #11
+set_include_path( dirname(dirname(dirname(dirname(__FILE__))))
+                  . DIRECTORY_SEPARATOR . 'library' );
+
+/**
+ * @see Zend_Pdf
+ */
+require_once 'Zend/Pdf.php';
+
+if (!isset($argv[1])) {
+    echo "USAGE: php demo.php <pdf_file> [<output_pdf_file>]\n";
+    exit;
+}
+
+try {
+    $pdf = Zend_Pdf::load($argv[1]);
+} catch (Zend_Pdf_Exception $e) {
+    if ($e->getMessage() == 'Can not open \'' . $argv[1] . '\' file for reading.') {
+        // Create new PDF if file doesn't exist
+        $pdf = new Zend_Pdf();
+
+        if (!isset($argv[2])) {
+            // force complete file rewriting (instead of updating)
+            $argv[2] = $argv[1];
+        }
+    } else {
+        // Throw an exception if it's not the "Can't open file" exception
+        throw $e;
+    }
+}
+
+//------------------------------------------------------------------------------------
+// Reverse page order
+$pdf->pages = array_reverse($pdf->pages);
+
+// Create new Style
+$style = new Zend_Pdf_Style();
+$style->setFillColor(new Zend_Pdf_Color_Rgb(0, 0, 0.9));
+$style->setLineColor(new Zend_Pdf_Color_GrayScale(0.2));
+$style->setLineWidth(3);
+$style->setLineDashingPattern(array(3, 2, 3, 4), 1.6);
+$style->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD), 32);
+
+try {
+    // Create new image object
+    $stampImage = Zend_Pdf_Image::imageWithPath(dirname(__FILE__) . '/stamp.jpg');
+} catch (Zend_Pdf_Exception $e) {
+    // Example of operating with image loading exceptions.
+    if ($e->getMessage() != 'Image extension is not installed.' &&
+        $e->getMessage() != 'JPG support is not configured properly.') {
+        throw $e;
+    }
+    $stampImage = null;
+}
+
+// Mark page as modified
+foreach ($pdf->pages as $page){
+    $page->saveGS()
+         ->setAlpha(0.25)
+         ->setStyle($style)
+         ->rotate(0, 0, M_PI_2/3);
+
+    $page->saveGS();
+    $page->clipCircle(550, -10, 50);
+    if ($stampImage != null) {
+        $page->drawImage($stampImage, 500, -60, 600, 40);
+    }
+    $page->restoreGS();
+
+    $page->drawText('Modified by Zend Framework!', 150, 0)
+         ->restoreGS();
+}
+
+// Add new page generated by Zend_Pdf object (page is attached to the specified the document)
+$pdf->pages[] = ($page1 = $pdf->newPage('A4'));
+
+// Add new page generated by Zend_Pdf_Page object (page is not attached to the document)
+$pdf->pages[] = ($page2 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE));
+
+// Create new font
+$font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
+
+// Apply font and draw text
+$page1->setFont($font, 36)
+      ->setFillColor(Zend_Pdf_Color_Html::color('#9999cc'))
+      ->drawText('Helvetica 36 text string', 60, 500);
+
+// Use font object for another page
+$page2->setFont($font, 24)
+      ->drawText('Helvetica 24 text string', 60, 500);
+
+// Use another font
+$page2->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES), 32)
+      ->drawText('Times-Roman 32 text string', 60, 450);
+
+// Draw rectangle
+$page2->setFillColor(new Zend_Pdf_Color_GrayScale(0.8))
+      ->setLineColor(new Zend_Pdf_Color_GrayScale(0.2))
+      ->setLineDashingPattern(array(3, 2, 3, 4), 1.6)
+      ->drawRectangle(60, 400, 400, 350);
+
+// Draw circle
+$page2->setLineDashingPattern(Zend_Pdf_Page::LINE_DASHING_SOLID)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawCircle(85, 375, 25);
+
+// Draw sectors
+$page2->drawCircle(200, 375, 25, 2*M_PI/3, -M_PI/6)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawCircle(200, 375, 25, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawCircle(200, 375, 25, -M_PI/6, M_PI/6);
+
+// Draw ellipse
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawEllipse(250, 400, 400, 350)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawEllipse(250, 400, 400, 350, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawEllipse(250, 400, 400, 350, -M_PI/6, M_PI/6);
+
+// Draw and fill polygon
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 1));
+$x = array();
+$y = array();
+for ($count = 0; $count < 8; $count++) {
+    $x[] = 140 + 25*cos(3*M_PI_4*$count);
+    $y[] = 375 + 25*sin(3*M_PI_4*$count);
+}
+$page2->drawPolygon($x, $y,
+                    Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE,
+                    Zend_Pdf_Page::FILL_METHOD_EVEN_ODD);
+
+// ---------- Draw figures in modified coordination system -----------------------------------
+
+// Coordination system movement
+$page2->saveGS();
+$page2->translate(60, 250); // Shift coordination system
+                    
+// Draw rectangle
+$page2->setFillColor(new Zend_Pdf_Color_GrayScale(0.8))
+      ->setLineColor(new Zend_Pdf_Color_GrayScale(0.2))
+      ->setLineDashingPattern(array(3, 2, 3, 4), 1.6)
+      ->drawRectangle(0, 50, 340, 0);
+
+// Draw circle
+$page2->setLineDashingPattern(Zend_Pdf_Page::LINE_DASHING_SOLID)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawCircle(25, 25, 25);
+
+// Draw sectors
+$page2->drawCircle(140, 25, 25, 2*M_PI/3, -M_PI/6)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawCircle(140, 25, 25, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawCircle(140, 25, 25, -M_PI/6, M_PI/6);
+
+// Draw ellipse
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawEllipse(190, 50, 340, 0)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawEllipse(190, 50, 340, 0, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawEllipse(190, 50, 340, 0, -M_PI/6, M_PI/6);
+
+// Draw and fill polygon
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 1));
+$x = array();
+$y = array();
+for ($count = 0; $count < 8; $count++) {
+    $x[] = 80 + 25*cos(3*M_PI_4*$count);
+    $y[] = 25 + 25*sin(3*M_PI_4*$count);
+}
+$page2->drawPolygon($x, $y,
+                    Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE,
+                    Zend_Pdf_Page::FILL_METHOD_EVEN_ODD);
+                    
+// Draw line
+$page2->setLineWidth(0.5)
+      ->drawLine(0, 25, 340, 25);
+
+$page2->restoreGS();
+
+
+// Coordination system movement, skewing and scaling
+$page2->saveGS();
+$page2->translate(60, 150)     // Shift coordination system
+      ->skew(0, 0, 0, -M_PI/9) // Skew coordination system
+      ->scale(0.9, 0.9);       // Scale coordination system
+                    
+// Draw rectangle
+$page2->setFillColor(new Zend_Pdf_Color_GrayScale(0.8))
+      ->setLineColor(new Zend_Pdf_Color_GrayScale(0.2))
+      ->setLineDashingPattern(array(3, 2, 3, 4), 1.6)
+      ->drawRectangle(0, 50, 340, 0);
+
+// Draw circle
+$page2->setLineDashingPattern(Zend_Pdf_Page::LINE_DASHING_SOLID)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawCircle(25, 25, 25);
+
+// Draw sectors
+$page2->drawCircle(140, 25, 25, 2*M_PI/3, -M_PI/6)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawCircle(140, 25, 25, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawCircle(140, 25, 25, -M_PI/6, M_PI/6);
+
+// Draw ellipse
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 0))
+      ->drawEllipse(190, 50, 340, 0)
+      ->setFillColor(new Zend_Pdf_Color_Cmyk(1, 0, 0, 0))
+      ->drawEllipse(190, 50, 340, 0, M_PI/6, 2*M_PI/3)
+      ->setFillColor(new Zend_Pdf_Color_Rgb(1, 1, 0))
+      ->drawEllipse(190, 50, 340, 0, -M_PI/6, M_PI/6);
+
+// Draw and fill polygon
+$page2->setFillColor(new Zend_Pdf_Color_Rgb(1, 0, 1));
+$x = array();
+$y = array();
+for ($count = 0; $count < 8; $count++) {
+    $x[] = 80 + 25*cos(3*M_PI_4*$count);
+    $y[] = 25 + 25*sin(3*M_PI_4*$count);
+}
+$page2->drawPolygon($x, $y,
+                    Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE,
+                    Zend_Pdf_Page::FILL_METHOD_EVEN_ODD);
+                    
+// Draw line
+$page2->setLineWidth(0.5)
+      ->drawLine(0, 25, 340, 25);
+
+$page2->restoreGS();
+
+//------------------------------------------------------------------------------------
+
+if (isset($argv[2])) {
+    $pdf->save($argv[2]);
+} else {
+    $pdf->save($argv[1], true /* update */);
+}

二進制
demos/Zend/Pdf/stamp.jpg


+ 47 - 0
demos/Zend/Pdf/test.pdf

@@ -0,0 +1,47 @@
+%PDF-1.4
+%âãÏÓ
+1 0 obj
+<< /Type /Catalog /Outlines 2 0 R /Pages 3 0 R >>
+endobj
+2 0 obj
+<< /Type /Outlines /Count 0 >>
+endobj
+3 0 obj
+<< /Type /Pages /Kids [4 0 R] /Count 1 >>
+endobj
+4 0 obj
+<< /Type /Page /Parent 3 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << /ProcSet 6 0 R /Font << /F1 7 0 R >> >> >>
+endobj
+5 0 obj
+<< /Length 47 >>
+stream
+BT
+  /F1 24 Tf
+  100 650 Td
+  (Test page) Tj
+ET
+endstream
+endobj
+6 0 obj
+[/PDF /Text]
+endobj
+7 0 obj
+<< /Type /Font /Subtype /Type1 /Name /F1 /BaseFont /Helvetica /Encoding /MacRomanEncoding >>
+endobj
+xref
+0 8
+0000000000 65535 f
+0000000015 00000 n
+0000000080 00000 n
+0000000126 00000 n
+0000000183 00000 n
+0000000324 00000 n
+0000000421 00000 n
+0000000449 00000 n
+trailer
+   << /Size 8
+      /Root 1 0 R
+   >>
+startxref
+557
+%%EOF

+ 165 - 0
demos/Zend/ProgressBar/JsPush.php

@@ -0,0 +1,165 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_ProgressBar
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * This sample file demonstrates a simple use case of a jspush-driven progressbar
+ */
+
+if (isset($_GET['progress'])) {
+    set_include_path(realpath(dirname(__FILE__) . '/../../../library')
+                     . PATH_SEPARATOR . get_include_path());
+    
+    require_once 'Zend/ProgressBar.php'; 
+    require_once 'Zend/ProgressBar/Adapter/JsPush.php';
+    
+    $adapter     = new Zend_ProgressBar_Adapter_JsPush(array('updateMethodName' => 'Zend_ProgressBar_Update',
+                                                             'finishMethodName' => 'Zend_ProgressBar_Finish')); 
+    $progressBar = new Zend_ProgressBar($adapter, 0, 100); 
+     
+    for ($i = 1; $i <= 100; $i++) {
+        if ($i < 20) {
+            $text = 'Just beginning';
+        } else if ($i < 50) {
+            $text = 'A bit done';
+        } else if ($i < 80) {
+            $text = 'Getting closer';
+        } else {
+            $text = 'Nearly done';
+        }
+        
+        $progressBar->update($i, $text); 
+        usleep(100000); 
+    } 
+    
+    $progressBar->finish();
+    
+    die;
+}
+?>
+<html>
+<head>
+    <title>Zend_ProgressBar Javascript Push Demo and Test</title>
+    <style type="text/css">   
+        iframe {
+            position: absolute;
+            left: -100px;
+            top: -100px;
+        
+            width: 10px;
+            height: 10px;
+            overflow: hidden;
+        }
+    
+        #progressbar {
+            position: absolute;
+            left: 10px;
+            top: 10px;
+        }
+        
+        .pg-progressbar {
+            position: relative;
+        
+            width: 250px;
+            height: 24px;
+            overflow: hidden;
+        
+            border: 1px solid #c6c6c6;
+        }
+        
+        .pg-progress {
+            z-index: 150;
+        
+            position: absolute;
+            left: 0;
+            top: 0;
+        
+            width: 0;
+            height: 24px;
+            overflow: hidden;
+        }
+        
+        .pg-progressstyle {
+            height: 22px;
+        
+            border: 1px solid #748a9e;
+            background-image: url('animation.gif');
+        }
+        
+        .pg-text,
+        .pg-invertedtext {
+            position: absolute;
+            left: 0;
+            top: 4px;
+        
+            width: 250px;
+        
+            text-align: center;
+            font-family: sans-serif;
+            font-size: 12px;
+        }
+        
+        .pg-invertedtext {
+            color: #ffffff;
+        }
+        
+        .pg-text {
+            z-index: 100;
+            color: #000000;
+        }
+    </style>
+    <script type="text/javascript">
+        function startProgress()
+        {
+            var iFrame = document.createElement('iframe');
+            document.getElementsByTagName('body')[0].appendChild(iFrame);
+            iFrame.src = 'JsPush.php?progress';
+        }
+        
+        function Zend_ProgressBar_Update(data)
+        {
+            document.getElementById('pg-percent').style.width = data.percent + '%';
+        
+            document.getElementById('pg-text-1').innerHTML = data.text;
+            document.getElementById('pg-text-2').innerHTML = data.text;
+        }
+        
+        function Zend_ProgressBar_Finish()
+        {
+            document.getElementById('pg-percent').style.width = '100%';
+        
+            document.getElementById('pg-text-1').innerHTML = 'Demo done';
+            document.getElementById('pg-text-2').innerHTML = 'Demo done';
+        }
+    </script>
+</head>
+<body onload="startProgress();">
+    <div id="progressbar">
+        <div class="pg-progressbar">
+            <div class="pg-progress" id="pg-percent">
+                <div class="pg-progressstyle"></div>
+                <div class="pg-invertedtext" id="pg-text-1"></div>
+            </div>
+            <div class="pg-text" id="pg-text-2"></div>
+        </div>
+    </div>
+    <div id="progressBar"><div id="progressDone"></div></div>
+</body>
+</html>

+ 218 - 0
demos/Zend/ProgressBar/Upload.php

@@ -0,0 +1,218 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_ProgressBar
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * This sample file demonstrates a simple use case of a jspull-driven progressbar
+ */
+
+if (isset($_GET['uploadId'])) {
+    set_include_path(realpath(dirname(__FILE__) . '/../../../library')
+                     . PATH_SEPARATOR . get_include_path());
+    
+    require_once 'Zend/ProgressBar.php'; 
+    require_once 'Zend/ProgressBar/Adapter/JsPull.php';
+    require_once 'Zend/Session/Namespace.php'; 
+   
+    $data          = uploadprogress_get_info($_GET['uploadId']);   
+    $bytesTotal    = ($data === null ? 0 : $data['bytes_total']);
+    $bytesUploaded = ($data === null ? 0 : $data['bytes_uploaded']);
+    
+    $adapter     = new Zend_ProgressBar_Adapter_JsPull();
+    $progressBar = new Zend_ProgressBar($adapter, 0, $bytesTotal, 'uploadProgress');
+    
+    if ($bytesTotal === $bytesUploaded) {
+        $progressBar->finish();
+    } else {
+        $progressBar->update($bytesUploaded);
+    }
+}
+?>
+<html>
+<head>
+    <title>Zend_ProgressBar Upload Demo</title>
+    <style type="text/css">   
+        iframe {
+            position: absolute;
+            left: -100px;
+            top: -100px;
+        
+            width: 10px;
+            height: 10px;
+            overflow: hidden;
+        }
+    
+        #progressbar {
+            position: absolute;
+            left: 10px;
+            top: 50px;
+        }
+        
+        .pg-progressbar {
+            position: relative;
+        
+            width: 250px;
+            height: 24px;
+            overflow: hidden;
+        
+            border: 1px solid #c6c6c6;
+        }
+        
+        .pg-progress {
+            z-index: 150;
+        
+            position: absolute;
+            left: 0;
+            top: 0;
+        
+            width: 0;
+            height: 24px;
+            overflow: hidden;
+        }
+        
+        .pg-progressstyle {
+            height: 22px;
+        
+            border: 1px solid #748a9e;
+            background-image: url('animation.gif');
+        }
+        
+        .pg-text,
+        .pg-invertedtext {
+            position: absolute;
+            left: 0;
+            top: 4px;
+        
+            width: 250px;
+        
+            text-align: center;
+            font-family: sans-serif;
+            font-size: 12px;
+        }
+        
+        .pg-invertedtext {
+            color: #ffffff;
+        }
+        
+        .pg-text {
+            z-index: 100;
+            color: #000000;
+        }
+    </style>
+    <script type="text/javascript">
+        function makeRequest(url)
+        {
+            var httpRequest;
+        
+            if (window.XMLHttpRequest) {
+                httpRequest = new XMLHttpRequest();
+                if (httpRequest.overrideMimeType) {
+                    httpRequest.overrideMimeType('text/xml');
+                }
+            } else if (window.ActiveXObject) {
+                try {
+                    httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
+                } catch (e) {
+                    try {
+                        httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
+                    } catch (e) {}
+                }
+            }
+        
+            if (!httpRequest) {
+                alert('Giving up :( Cannot create an XMLHTTP instance');
+                return false;
+            }
+            
+            httpRequest.onreadystatechange = function() { evalProgress(httpRequest); };
+            httpRequest.open('GET', url, true);
+            httpRequest.send('');
+        
+        }
+    
+        function observeProgress()
+        {
+            setTimeout("getProgress()", 1500);
+        }
+        
+        function getProgress()
+        {
+            makeRequest('Upload.php?uploadId=' + document.getElementById('uploadId').value);
+        }
+
+        function evalProgress(httpRequest)
+        {
+            try {
+                if (httpRequest.readyState == 4) {
+                    if (httpRequest.status == 200) {
+                        eval('var data = ' + httpRequest.responseText);
+
+                        if (data.finished) {
+                            finish();
+                        } else {
+                            update(data);
+                            setTimeout("getProgress()", 1000);
+                        }
+                    } else {
+                        alert('There was a problem with the request.');
+                    }
+                }
+            } catch(e) {
+                alert('Caught Exception: ' + e.description);
+            }
+        }
+        
+        function update(data)
+        {
+            document.getElementById('pg-percent').style.width = data.percent + '%';
+
+            document.getElementById('pg-text-1').innerHTML = data.timeRemaining + ' seconds remaining';
+            document.getElementById('pg-text-2').innerHTML = data.timeRemaining + ' seconds remaining';
+        }
+        
+        function finish()
+        {
+            document.getElementById('pg-percent').style.width = '100%';
+        
+            document.getElementById('pg-text-1').innerHTML = 'Upload done';
+            document.getElementById('pg-text-2').innerHTML = 'Upload done';
+        }
+    </script>
+</head>
+<body>
+    <form enctype="multipart/form-data" method="post" action="Upload.php" target="uploadTarget" onsubmit="observeProgress();">
+        <input type="hidden" name="UPLOAD_IDENTIFIER" id="uploadId" value="<?php echo md5(uniqid(rand())); ?>" />
+        <input type="file" name="file" />
+        <input type="submit" value="Upload!" />
+    </form>
+    <iframe name="uploadTarget"></iframe>
+
+    <div id="progressbar">
+        <div class="pg-progressbar">
+            <div class="pg-progress" id="pg-percent">
+                <div class="pg-progressstyle"></div>
+                <div class="pg-invertedtext" id="pg-text-1"></div>
+            </div>
+            <div class="pg-text" id="pg-text-2"></div>
+        </div>
+    </div>
+    <div id="progressBar"><div id="progressDone"></div></div>
+</body>
+</html>

+ 225 - 0
demos/Zend/ProgressBar/ZendForm.php

@@ -0,0 +1,225 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_ProgressBar
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * This sample file demonstrates an advanced use case of Zend_ProgressBar with
+ * Zend_Form and Zend_File_Transfer.
+ */
+
+set_include_path(realpath(dirname(__FILE__) . '/../../../library')
+                 . PATH_SEPARATOR . get_include_path());
+
+if (isset($_GET['progress_key'])) {
+    require_once 'Zend/File/Transfer/Adapter/Http.php';
+    require_once 'Zend/ProgressBar.php';
+    require_once 'Zend/ProgressBar/Adapter/JsPull.php';
+
+    $adapter = new Zend_ProgressBar_Adapter_JsPull();
+    Zend_File_Transfer_Adapter_Http::getProgress(array('progress' => $adapter));
+    die;
+}
+?>
+<html>
+<head>
+    <title>Zend_ProgressBar Upload Demo</title>
+    <style type="text/css">
+        iframe {
+            position: absolute;
+            left: -100px;
+            top: -100px;
+
+            width: 10px;
+            height: 10px;
+            overflow: hidden;
+        }
+
+        #progressbar {
+            position: absolute;
+            left: 10px;
+            top: 120px;
+        }
+
+        .pg-progressbar {
+            position: relative;
+
+            width: 250px;
+            height: 24px;
+            overflow: hidden;
+
+            border: 1px solid #c6c6c6;
+        }
+
+        .pg-progress {
+            z-index: 150;
+
+            position: absolute;
+            left: 0;
+            top: 0;
+
+            width: 0;
+            height: 24px;
+            overflow: hidden;
+        }
+
+        .pg-progressstyle {
+            height: 22px;
+
+            border: 1px solid #748a9e;
+            background-image: url('animation.gif');
+        }
+
+        .pg-text,
+        .pg-invertedtext {
+            position: absolute;
+            left: 0;
+            top: 4px;
+
+            width: 250px;
+
+            text-align: center;
+            font-family: sans-serif;
+            font-size: 12px;
+        }
+
+        .pg-invertedtext {
+            color: #ffffff;
+        }
+
+        .pg-text {
+            z-index: 100;
+            color: #000000;
+        }
+    </style>
+    <script type="text/javascript">
+        function makeRequest(url)
+        {
+            var httpRequest;
+
+            if (window.XMLHttpRequest) {
+                httpRequest = new XMLHttpRequest();
+                if (httpRequest.overrideMimeType) {
+                    httpRequest.overrideMimeType('text/xml');
+                }
+            } else if (window.ActiveXObject) {
+                try {
+                    httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
+                } catch (e) {
+                    try {
+                        httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
+                    } catch (e) {}
+                }
+            }
+
+            if (!httpRequest) {
+                alert('Giving up :( Cannot create an XMLHTTP instance');
+                return false;
+            }
+
+            httpRequest.onreadystatechange = function() { evalProgress(httpRequest); };
+            httpRequest.open('GET', url, true);
+            httpRequest.send('');
+
+        }
+
+        function observeProgress()
+        {
+            setTimeout("getProgress()", 1500);
+        }
+
+        function getProgress()
+        {
+            makeRequest('ZendForm.php?progress_key=' + document.getElementById('progress_key').value);
+        }
+
+        function evalProgress(httpRequest)
+        {
+            try {
+                if (httpRequest.readyState == 4) {
+                    if (httpRequest.status == 200) {
+                        eval('var data = ' + httpRequest.responseText);
+
+                        if (data.finished) {
+                            finish();
+                        } else {
+                            update(data);
+                            setTimeout("getProgress()", 1000);
+                        }
+                    } else {
+                        alert('There was a problem with the request.');
+                    }
+                }
+            } catch(e) {
+                alert('Caught Exception: ' + e.description);
+            }
+        }
+
+        function update(data)
+        {
+            document.getElementById('pg-percent').style.width = data.percent + '%';
+
+            document.getElementById('pg-text-1').innerHTML = data.text;
+            document.getElementById('pg-text-2').innerHTML = data.text;
+        }
+
+        function finish()
+        {
+            document.getElementById('pg-percent').style.width = '100%';
+
+            document.getElementById('pg-text-1').innerHTML = 'Upload done';
+            document.getElementById('pg-text-2').innerHTML = 'Upload done';
+        }
+    </script>
+</head>
+<body>
+    <?php
+    require_once 'Zend/View.php';
+    require_once 'Zend/Form.php';
+
+    $form = new Zend_Form(array(
+        'enctype'  => 'multipart/form-data',
+        'action'   => 'ZendForm.php',
+        'target'   => 'uploadTarget',
+        'onsubmit' => 'observeProgress();',
+        'elements' => array(
+            'file'   => array('file', array('label' => 'File')),
+            'submit' => array('submit', array('label' => 'Upload!'))
+        )
+    ));
+
+    $form->setView(new Zend_View());
+
+    echo $form;
+    ?>
+    <iframe name="uploadTarget"></iframe>
+
+    <div id="progressbar">
+        <div class="pg-progressbar">
+            <div class="pg-progress" id="pg-percent">
+                <div class="pg-progressstyle"></div>
+                <div class="pg-invertedtext" id="pg-text-1"></div>
+            </div>
+            <div class="pg-text" id="pg-text-2"></div>
+        </div>
+    </div>
+    <div id="progressBar"><div id="progressDone"></div></div>
+</body>
+</html>
+

二進制
demos/Zend/ProgressBar/animation.gif


+ 57 - 0
demos/Zend/Search/Lucene/feed-search/create-index.php

@@ -0,0 +1,57 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Search_Lucene
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Feed
+ */
+require_once 'Zend/Feed.php';
+
+/**
+ * @see Zend_Search_Lucene
+ */
+require_once 'Zend/Search/Lucene.php';
+
+//create the index
+$index = new Zend_Search_Lucene('/tmp/feeds_index', true);
+
+// index each item
+$rss = Zend_Feed::import('http://feeds.feedburner.com/ZendDeveloperZone');
+
+foreach ($rss->items as $item) {
+    $doc = new Zend_Search_Lucene_Document();
+
+    if ($item->link && $item->title && $item->description) {
+
+        $link = htmlentities(strip_tags( $item->link() ));
+        $doc->addField(Zend_Search_Lucene_Field::UnIndexed('link', $link));
+
+        $title = htmlentities(strip_tags( $item->title() ));
+        $doc->addField(Zend_Search_Lucene_Field::Text('title', $title));
+
+        $contents = htmlentities(strip_tags( $item->description() ));
+        $doc->addField(Zend_Search_Lucene_Field::Text('contents', $contents));
+
+        echo "Adding {$item->title()}...\n";
+        $index->addDocument($doc);
+    }
+}
+
+$index->commit();

+ 43 - 0
demos/Zend/Search/Lucene/feed-search/search-index.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Search_Lucene
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Search_Lucene
+ */
+require_once 'Zend/Search/Lucene.php';
+
+$index = new Zend_Search_Lucene('/tmp/feeds_index');
+echo "Index contains {$index->count()} documents.\n";
+
+$search = 'php';
+$hits   = $index->find(strtolower($search));
+echo "Search for \"$search\" returned " .count($hits). " hits.\n\n";
+
+foreach ($hits as $hit) {
+    echo str_repeat('-', 80) . "\n";
+    echo 'ID:    ' . $hit->id                     ."\n";
+    echo 'Score: ' . sprintf('%.2f', $hit->score) ."\n\n";
+
+    foreach ($hit->getDocument()->getFieldNames() as $field) {
+        echo "$field: \n";
+        echo '    ' . trim(substr($hit->$field,0,76)) . "\n";
+    }
+}

+ 97 - 0
demos/Zend/Search/Lucene/indexing/CreateIndex.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Search_Lucene
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_Search_Lucene
+ */
+require_once 'Zend/Search/Lucene.php';
+
+/**
+ * @category   Zend
+ * @package    Zend_Search_Lucene
+ * @subpackage Demos
+ * @uses       Zend_Search_Lucene_Document
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class FileDocument extends Zend_Search_Lucene_Document
+{
+    /**
+     * Object constructor
+     *
+     * @param  string  $fileName
+     * @param  boolean $storeContent
+     * @throws Zend_Search_Lucene_Exception
+     * @return void
+     */
+    public function __construct($fileName, $storeContent = false)
+    {
+        if (!file_exists($fileName)) {
+            throw new Zend_Search_Lucene_Exception("File doesn't exists. Filename: '$fileName'");
+        }
+        $this->addField(Zend_Search_Lucene_Field::Text('path', $fileName));
+        $this->addField(Zend_Search_Lucene_Field::Keyword( 'modified', filemtime($fileName) ));
+
+        $f = fopen($fileName,'rb');
+        $byteCount = filesize($fileName);
+
+        $data = '';
+        while ( $byteCount > 0 && ($nextBlock = fread($f, $byteCount)) != false ) {
+            $data .= $nextBlock;
+            $byteCount -= strlen($nextBlock);
+        }
+        fclose($f);
+
+        if ($storeContent) {
+            $this->addField(Zend_Search_Lucene_Field::Text('contents', $data, 'ISO8859-1'));
+        } else {
+            $this->addField(Zend_Search_Lucene_Field::UnStored('contents', $data, 'ISO8859-1'));
+        }
+    }
+}
+
+
+// Create index
+$index = new Zend_Search_Lucene('index', true);
+// Uncomment next line if you want to have case sensitive index
+// ZSearchAnalyzer::setDefault(new ZSearchTextAnalyzer());
+
+setlocale(LC_CTYPE, 'en_US');
+
+$indexSourceDir = 'IndexSource';
+$dir = opendir($indexSourceDir);
+while (($file = readdir($dir)) !== false) {
+    if (is_dir($indexSourceDir . '/' . $file)) {
+        continue;
+    }
+    if (strcasecmp(substr($file, strlen($file)-5), '.html') != 0) {
+        continue;
+    }
+
+    // Create new Document from a file
+    $doc = new FileDocument($indexSourceDir . '/' . $file, true);
+    // Add document to the index
+    $index->addDocument($doc);
+
+    echo $file . "...\n";
+    flush();
+}
+closedir($dir);

+ 179 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/about-pear.html

@@ -0,0 +1,179 @@
+<HTML
+><HEAD
+><TITLE
+>About PEAR</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Authors and Contributors"
+HREF="authors.html"><LINK
+REL="NEXT"
+TITLE="Introduction"
+HREF="introduction.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="part"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="authors.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="introduction.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="PART"
+><A
+NAME="about-pear"><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="title"
+>I. About PEAR</H1
+></DIV
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>1. <A
+HREF="introduction.html"
+>Introduction</A
+></DT
+><DT
+>2. <A
+HREF="installation.html"
+>Installation</A
+></DT
+><DT
+>3. <A
+HREF="support.html"
+>Support</A
+></DT
+><DT
+>4. <A
+HREF="standards.html"
+>Coding Standards</A
+></DT
+><DT
+>5. <A
+HREF="contributing.html"
+>Contributing</A
+></DT
+><DT
+>6. <A
+HREF="faq.html"
+>FAQ - Frequently Asked Questions</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="authors.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="introduction.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Authors and Contributors</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Introduction</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 235 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/authors.html

@@ -0,0 +1,235 @@
+<HTML
+><HEAD
+><TITLE
+>Authors and Contributors</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Preface"
+HREF="preface.html"><LINK
+REL="PREVIOUS"
+TITLE="The structure of the Manual"
+HREF="structure.html"><LINK
+REL="NEXT"
+TITLE="About PEAR"
+HREF="about-pear.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="structure.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Preface</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="about-pear.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="authors">Authors and Contributors</H1
+><P
+>&#13;    The following is a list of people that are helping to maintain this
+    documentation. If you would like to contact one of them, please
+    write to <A
+HREF="mailto:pear-doc@lists.php.net"
+TARGET="_top"
+>pear-doc@lists.php.net</A
+>.
+   </P
+><P
+></P
+><UL
+><LI
+><P
+>&#13;   Lorenzo Alberton
+  </P
+></LI
+><LI
+><P
+>&#13;   Gregory Beaver
+  </P
+></LI
+><LI
+><P
+>&#13;   Daniel Convissor
+  </P
+></LI
+><LI
+><P
+>&#13;   David Costa
+  </P
+></LI
+><LI
+><P
+>&#13;   Thomas V.V. Cox
+  </P
+></LI
+><LI
+><P
+>&#13;   Martin Jansen
+  </P
+></LI
+><LI
+><P
+>&#13;   Alan Knowles
+  </P
+></LI
+><LI
+><P
+>&#13;   Clay Loveless
+  </P
+></LI
+><LI
+><P
+>&#13;   Alexander Merz
+  </P
+></LI
+><LI
+><P
+>&#13;   Stefan Neufeind
+  </P
+></LI
+><LI
+><P
+>&#13;   Jon Parise
+  </P
+></LI
+><LI
+><P
+>&#13;   Tobias Schlitt
+  </P
+></LI
+><LI
+><P
+>&#13;   Stephan Schmidt
+  </P
+></LI
+><LI
+><P
+>&#13;   Mika Tuupola
+  </P
+></LI
+><LI
+><P
+>&#13;   Michael Wallner
+  </P
+></LI
+></UL
+><P
+>&#13;    (In alphabetic order.)
+   </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="structure.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="about-pear.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>The structure of the Manual</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="preface.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>About PEAR</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 185 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.bugs.html

@@ -0,0 +1,185 @@
+<HTML
+><HEAD
+><TITLE
+>Reporting Bugs</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="PREVIOUS"
+TITLE="Submitting Patches"
+HREF="contributing.patches.html"><LINK
+REL="NEXT"
+TITLE="Writing & Translating Documentation"
+HREF="contributing.documentation.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="contributing.patches.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Contributing</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="contributing.documentation.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.bugs">Reporting Bugs</H1
+><P
+>&#13;   If you think that you have found a bug in a PEAR package, please
+   take care that you are using the latest version of the package  and
+   that your system does meet the packages' requirements.
+  </P
+><P
+>&#13;    If the bug still persists with the latest version of the package, 
+    don't hesitate to fill out a bug report. The easiest way is to click
+    to link <SPAN
+CLASS="QUOTE"
+>"Package Bugs"</SPAN
+> on the package information page
+    for the package on the <A
+HREF="http://pear.php.net/"
+TARGET="_top"
+>PEAR
+    Homepage</A
+>, which you think contains a bug. This will take you
+    to a list of existing bugs of the package. Please double check if
+    the bug hasn't already been reported! If you are unable to find it
+    in the list, you can click on <SPAN
+CLASS="QUOTE"
+>"Report a new bug"</SPAN
+> to
+    fill out the bug form.
+   </P
+><P
+>&#13;   More information and tips on how to report bugs in a proper way
+   can be found at <A
+HREF="http://bugs.php.net/how-to-report.php"
+TARGET="_top"
+>http://bugs.php.net/how-to-report.php</A
+>.
+  </P
+><P
+>&#13;   If you have already fixed a bug that you have found in a package, please
+   read <A
+HREF="contributing.patches.html"
+>this</A
+>.
+  </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="contributing.patches.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="contributing.documentation.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Submitting Patches</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Writing &#38; Translating Documentation</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 165 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.documentation.html

@@ -0,0 +1,165 @@
+<HTML
+><HEAD
+><TITLE
+>Writing &#38; Translating Documentation</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="PREVIOUS"
+TITLE="Reporting Bugs"
+HREF="contributing.bugs.html"><LINK
+REL="NEXT"
+TITLE="Wishlists"
+HREF="contributing.wishlist.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="contributing.bugs.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Contributing</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="contributing.wishlist.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.documentation">Writing &#38; Translating Documentation</H1
+><P
+>&#13;   Good documentation is essential for users to fully understand any
+   software. Several PEAR packages lack documentation or have docs which
+   need improvement. <A
+HREF="developers.documentation.html"
+>Writing documentation</A
+>
+   provides more information about helping out on this front.
+  </P
+><P
+>&#13;   Translating documentation is another important task.
+   Not only does new documentation need to be translated
+   into the existing languages, additional languages are
+   welcome. Also, existing translations need to be brought
+   up to date because the English versions have been changed.
+   Help on how to perform the translation process is in the <A
+HREF="faq.translators-revision-tracking.html"
+>Revision Tracking</A
+>
+   section of the manual.
+  </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="contributing.bugs.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="contributing.wishlist.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Reporting Bugs</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Wishlists</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 201 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.html

@@ -0,0 +1,201 @@
+<HTML
+><HEAD
+><TITLE
+>Contributing</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="About PEAR"
+HREF="about-pear.html"><LINK
+REL="PREVIOUS"
+TITLE="Sample File (including Docblock Comment standards)"
+HREF="standards.sample.html"><LINK
+REL="NEXT"
+TITLE="Writing New Packages"
+HREF="contributing.newpackage.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="chapter"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="standards.sample.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="contributing.newpackage.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="chapter"
+><H1
+><A
+NAME="contributing"
+>Chapter 5. Contributing</A
+></H1
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+><A
+HREF="contributing.html#contributing.introduction"
+>Introduction</A
+></DT
+><DT
+><A
+HREF="contributing.newpackage.html"
+>Writing New Packages</A
+></DT
+><DT
+><A
+HREF="contributing.patches.html"
+>Submitting Patches</A
+></DT
+><DT
+><A
+HREF="contributing.bugs.html"
+>Reporting Bugs</A
+></DT
+><DT
+><A
+HREF="contributing.documentation.html"
+>Writing &#38; Translating Documentation</A
+></DT
+><DT
+><A
+HREF="contributing.wishlist.html"
+>Wishlists</A
+></DT
+></DL
+></DIV
+><P
+>&#13;  This chapter describes the different ways in which one could
+  contribute to PEAR.
+ </P
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.introduction">Introduction</H1
+><P
+>&#13;   PEAR is an open source project, which means that everyone can help
+   improving it. Improving doesn't always mean writing code. It can 
+   also mean reporting bugs, giving feedback, submitting feature
+   requests and even financial support.
+  </P
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="standards.sample.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="contributing.newpackage.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Sample File (including Docblock Comment standards)</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="about-pear.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Writing New Packages</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 151 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.newpackage.html

@@ -0,0 +1,151 @@
+<HTML
+><HEAD
+><TITLE
+>Writing New Packages</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="PREVIOUS"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="NEXT"
+TITLE="Submitting Patches"
+HREF="contributing.patches.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="contributing.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Contributing</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="contributing.patches.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.newpackage">Writing New Packages</H1
+><P
+>&#13;   Explanations about submitting new packages can be found in the
+   <A
+HREF="guide-developers.html"
+>Developers' Guide</A
+>.
+  </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="contributing.patches.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Contributing</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Submitting Patches</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 274 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.patches.html

@@ -0,0 +1,274 @@
+<HTML
+><HEAD
+><TITLE
+>Submitting Patches</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="PREVIOUS"
+TITLE="Writing New Packages"
+HREF="contributing.newpackage.html"><LINK
+REL="NEXT"
+TITLE="Reporting Bugs"
+HREF="contributing.bugs.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="contributing.newpackage.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Contributing</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="contributing.bugs.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.patches">Submitting Patches</H1
+><P
+>&#13;   If you have modified a package to expand its functionality or to fix a
+   bug, you should contribute your changes back to the community (some
+   licenses force you to do so, and it is generally considered immoral not to).
+  </P
+><P
+>&#13;   Before creating the patch, you must first obtain the latest sources of the
+   package you wish to patch from CVS by running the commands (the package
+   in this example is Foo_Bar):
+   <TABLE
+WIDTH="100%"
+CELLSPACING="0"
+CELLPADDING="0"
+BORDER="0"
+BGCOLOR="#EEEEEE"
+><TR
+><TD
+><PRE
+CLASS="screen"
+><TT
+CLASS="userinput"
+><B
+>&#13;cvs -d:pserver:cvsread@cvs.php.net:/repository login
+</B
+></TT
+>
+password is <TT
+CLASS="userinput"
+><B
+>phpfi</B
+></TT
+>
+<TT
+CLASS="userinput"
+><B
+>&#13;cvs -d:pserver:cvsread@cvs.php.net:/repository co pear/Foo_Bar
+</B
+></TT
+></PRE
+></TD
+></TR
+></TABLE
+>
+   Now that you have the latest sources, you can edit the relevant file(s).
+   Make sure that your patch is fully compatible with the PEAR <A
+HREF="standards.html"
+>coding
+standards.</A
+>.
+  </P
+><P
+>&#13;   After you have finished adding/changing the code, TEST it: We will not
+   accept code that hasn't been carefully tested.
+   When you are absolutely sure the new code doesn't introduce bugs, create a
+   unified diff by running:
+   <TABLE
+WIDTH="100%"
+CELLSPACING="0"
+CELLPADDING="0"
+BORDER="0"
+BGCOLOR="#EEEEEE"
+><TR
+><TD
+><PRE
+CLASS="screen"
+><TT
+CLASS="userinput"
+><B
+>cd pear/Foo_Bar</B
+></TT
+>
+<TT
+CLASS="userinput"
+><B
+>cvs diff -u &#62;Foo_Bar.diff</B
+></TT
+></PRE
+></TD
+></TR
+></TABLE
+>
+   The resulting .diff file contains your patch. This diff makes it easy
+   for us to see what has been changed.
+  </P
+><P
+>&#13;   Next step is to submit the patch. Send a mail to pear-dev@lists.php.net and
+   Cc the maintainer(s) of the package. The subject of the mail should be
+   prefixed with '[Patch]' to make it clear you are submitting a patch. Also
+   include a verbose explanation of what the patch does.
+   Don't forget to attach the .diff file to the mail. The maintainers of
+   the package are usually listed in the header of each source file. Apart
+   from that their email adresses are available on the package information
+   page on http://pear.php.net/.
+  </P
+><DIV
+CLASS="note"
+><BLOCKQUOTE
+CLASS="note"
+><P
+><B
+>Note: </B
+>
+    If you are using Outlook or Outlook Express, please change the file
+    extension of the diff file to .txt, because Outlook's MIME-Type
+    detection depends on the file extension and attachments with a
+    MIME-Type not equal to <TT
+CLASS="literal"
+>text/plain</TT
+> will be rejected
+    by our mailinglist software.
+   </P
+></BLOCKQUOTE
+></DIV
+><DIV
+CLASS="note"
+><BLOCKQUOTE
+CLASS="note"
+><P
+><B
+>Note: </B
+>
+    If your patch does break backwards compatibility, the chances are fairly
+    good that the maintainers won't be happy about it. Thus you should always
+    try to fix a bug in a way that does not seriously change the public API.
+    But if there is absolutely no way to keep backwards compatibility and/or
+    if your patch contains a groundbraking improvement, even API changes are
+    fine.
+   </P
+></BLOCKQUOTE
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="contributing.newpackage.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="contributing.bugs.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Writing New Packages</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>Reporting Bugs</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 158 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/contributing.wishlist.html

@@ -0,0 +1,158 @@
+<HTML
+><HEAD
+><TITLE
+>Wishlists</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="UP"
+TITLE="Contributing"
+HREF="contributing.html"><LINK
+REL="PREVIOUS"
+TITLE="Writing & Translating Documentation"
+HREF="contributing.documentation.html"><LINK
+REL="NEXT"
+TITLE="FAQ - Frequently Asked Questions"
+HREF="faq.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="sect1"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="contributing.documentation.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+>Chapter 5. Contributing</TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="faq.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="sect1"
+><H1
+CLASS="sect1"
+><A
+NAME="contributing.wishlist">Wishlists</H1
+><P
+>&#13;   Some of the PEAR developers have wishlists at Amazon or a similar
+   service. If you appreciate the work of a certain developer, feel
+   free to buy something for her from her wishlist. To find out if
+   a developer has one of those wishlists, go to the 
+   <A
+HREF="http://pear.php.net/accounts.php"
+TARGET="_top"
+>account browser</A
+>,
+   look for the details of the developer and there you'll see if she
+   has a wishlist. Buying something from people's wishlists may even
+   speed up the time in which annoying bugs are fixed ;-).
+   </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="contributing.documentation.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="faq.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Writing &#38; Translating Documentation</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="contributing.html"
+ACCESSKEY="U"
+>Up</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>FAQ - Frequently Asked Questions</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 196 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/copyright.html

@@ -0,0 +1,196 @@
+<HTML
+><HEAD
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="NEXT"
+TITLE="Preface"
+HREF="preface.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="legalnotice"
+><A
+NAME="copyright"><P
+><B
+>Copyright</B
+></P
+><P
+>&#13;    Copyright © 2001 - 2006 by the PEAR Documentation Group.
+    This material may be distributed only subject to the terms and
+    conditions set forth in the Open Publication License, v1.0 or
+    later (the latest version is presently available at
+    <A
+HREF="http://www.opencontent.org/openpub/"
+TARGET="_top"
+>http://www.opencontent.org/openpub/</A
+>).
+   </P
+><P
+>&#13;    Distribution of substantively modified versions of this document
+    is prohibited without the explicit permission of the copyright
+    holder.
+   </P
+><P
+>&#13;    Distribution of the work or derivative of the work in any
+    standard (paper) book form is prohibited unless prior permission
+    is obtained from the copyright holder.
+   </P
+><P
+>&#13;    The PEAR Documentation Group consists of all the people that
+    have contributed documentation to the PEAR manual.
+    Representatives are listed on the front page of this manual.
+    In case you would like to contact the group, please write to 
+    <A
+HREF="mailto:pear-doc@lists.php.net"
+TARGET="_top"
+>pear-doc@lists.php.net</A
+>.
+   </P
+></DIV
+><DIV
+CLASS="legalnotice"
+><A
+NAME="copyright.xml-rpc"><P
+><B
+>&#13;    Copyright for the XML_RPC documentation
+   </B
+></P
+><P
+>&#13;    The <A
+HREF="package.webservices.xml-rpc.html"
+>documentation</A
+>
+    of the XML_RPC package has originally been written by <A
+HREF="http://usefulinc.com/"
+TARGET="_top"
+>Edd Dumbill</A
+> as an independent
+    document on <A
+HREF="http://xmlrpc.usefulinc.com/doc/"
+TARGET="_top"
+>his
+    homepage</A
+> and is published as part of the PEAR Manual under
+    the following license restrictions:
+   </P
+><P
+>&#13;    Copyright © 1999,2000,2001 by Edd Dumbill, Useful Information Company
+   </P
+><P
+>&#13;    All rights reserved.
+   </P
+><P
+>&#13;    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+   </P
+><P
+>&#13;	<P
+></P
+><UL
+><LI
+><P
+>&#13;       Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+      </P
+></LI
+><LI
+><P
+>&#13;       Redistributions in binary form must reproduce the above
+       copyright notice, this list of conditions and the following
+       disclaimer in the documentation and/or other materials provided
+       with the distribution.
+      </P
+></LI
+><LI
+><P
+>&#13;       Neither the name of the "XML-RPC for PHP" nor the names of its
+       contributors may be used to endorse or promote products derived
+       from this software without specific prior written permission.
+      </P
+></LI
+></UL
+>
+   </P
+><P
+> 
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+    CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+    DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+    OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+    OF SUCH DAMAGE.
+   </P
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>&nbsp;</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 159 - 0
demos/Zend/Search/Lucene/indexing/IndexSource/core.html

@@ -0,0 +1,159 @@
+<HTML
+><HEAD
+><TITLE
+>Core components</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.76b+
+"><LINK
+REL="HOME"
+TITLE="PEAR Manual"
+HREF="index.html"><LINK
+REL="PREVIOUS"
+TITLE="Structure of a post-install script"
+HREF="guide.migrating.postinstall.structure.html"><LINK
+REL="NEXT"
+TITLE="PEAR base classes"
+HREF="core.pear.html"><META
+HTTP-EQUIV="Content-type"
+CONTENT="text/html; charset=ISO-8859-1"></HEAD
+><BODY
+CLASS="part"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="NAVHEADER"
+><TABLE
+SUMMARY="Header navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TH
+COLSPAN="3"
+ALIGN="center"
+>PEAR Manual</TH
+></TR
+><TR
+><TD
+WIDTH="10%"
+ALIGN="left"
+VALIGN="bottom"
+><A
+HREF="guide.migrating.postinstall.structure.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="80%"
+ALIGN="center"
+VALIGN="bottom"
+></TD
+><TD
+WIDTH="10%"
+ALIGN="right"
+VALIGN="bottom"
+><A
+HREF="core.pear.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+></TABLE
+><HR
+ALIGN="LEFT"
+WIDTH="100%"></DIV
+><DIV
+CLASS="PART"
+><A
+NAME="core"><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="title"
+>V. Core components</H1
+></DIV
+><DIV
+CLASS="TOC"
+><DL
+><DT
+><B
+>Table of Contents</B
+></DT
+><DT
+>26. <A
+HREF="core.pear.html"
+>PEAR base classes</A
+></DT
+><DT
+>27. <A
+HREF="core.ppm.html"
+>PEAR Installer classes</A
+></DT
+></DL
+></DIV
+></DIV
+><DIV
+CLASS="NAVFOOTER"
+><HR
+ALIGN="LEFT"
+WIDTH="100%"><TABLE
+SUMMARY="Footer navigation table"
+WIDTH="100%"
+BORDER="0"
+CELLPADDING="0"
+CELLSPACING="0"
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+><A
+HREF="guide.migrating.postinstall.structure.html"
+ACCESSKEY="P"
+>Prev</A
+></TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+><A
+HREF="index.html"
+ACCESSKEY="H"
+>Home</A
+></TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+><A
+HREF="core.pear.html"
+ACCESSKEY="N"
+>Next</A
+></TD
+></TR
+><TR
+><TD
+WIDTH="33%"
+ALIGN="left"
+VALIGN="top"
+>Structure of a post-install script</TD
+><TD
+WIDTH="34%"
+ALIGN="center"
+VALIGN="top"
+>&nbsp;</TD
+><TD
+WIDTH="33%"
+ALIGN="right"
+VALIGN="top"
+>PEAR base classes</TD
+></TR
+></TABLE
+></DIV
+></BODY
+></HTML
+>

+ 214 - 0
demos/Zend/WebServices/Amazon/amazon-search.php

@@ -0,0 +1,214 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Amazon
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Query Amazon's Product Database
+ */
+
+/**
+ * @see Zend_Service_Amazon_Query
+ */
+require_once 'Zend/Service/Amazon/Query.php';
+
+$keywords = '';
+$searchFor = '';
+
+if (isset($_POST) && strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+    if (isset($_POST['search_term'])) {
+        $keywords = strip_tags($_POST['search_term']);
+    }
+
+    if (isset($_POST['search_type'])) {
+        $searchFor = strip_tags($_POST['search_type']);
+    }
+}
+?>
+<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+    <style type="text/css">
+        html, body {
+            margin: 0px;
+            padding: 0px;
+            font-family: Tahoma, Verdana, sans-serif;
+            font-size: 10px;
+        }
+
+        h1 {
+            margin-top: 0px;
+            background-color: darkblue;
+            color: white;
+            font-size: 16px;
+        }
+
+        form {
+            text-align: center;
+        }
+
+        label {
+            font-weight: bold;
+        }
+
+        img {
+            border: 0px;
+            padding: 5px;
+        }
+
+        #results {
+            margin-left: 30px;
+        }
+
+        #results .thumb {
+            clear: left;
+            float: left;
+        }
+
+         #results .details  {
+            clear: right;
+            float: left;
+         }
+
+
+        h2 {
+            font-size: 14px;
+            color: grey;
+        }
+
+        h3 {
+            clear:  both;
+            font-size: 12px;
+        }
+
+        #poweredby {
+            clear: both;
+        }
+    </style>
+</head>
+<body>
+    <h1>Amazon Product Search</h1>
+    <form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
+        <p>
+            <label>Search For: <input type="text" name="search_term" value="<?php echo htmlspecialchars($keywords, ENT_QUOTES); ?>"></label>
+            <label>
+                in
+                <select name="search_type">
+                <?php
+                $search_types = array (
+                0 => 'Apparel',
+                1 => 'Baby',
+                2 => 'Beauty',
+                3 => 'Blended',
+                4 => 'Books',
+                5 => 'Classical',
+                6 => 'DVD',
+                7 => 'Digital Music',
+                8 => 'Electronics',
+                9 => 'Gourmet Food',
+                10 => 'Health Personal Care',
+                11 => 'Jewelry',
+                12 => 'Kitchen',
+                13 => 'Magazines',
+                14 => 'Merchants',
+                15 => 'Miscellaneous',
+                16 => 'Music',
+                17 => 'Music Tracks',
+                18 => 'Musical Instruments',
+                19 => 'Office Products',
+                20 => 'Outdoor Living',
+                21 => 'PC Hardware',
+                22 => 'Pet Supplies',
+                23 => 'Photo',
+                24 => 'Restaurants',
+                25 => 'Software',
+                26 => 'Sporting Goods',
+                27 => 'Tools',
+                28 => 'Toys',
+                29 => 'VHS',
+                30 => 'Video',
+                31 => 'Video Games',
+                32 => 'Wireless',
+                33 => 'Wireless Accessories',
+                );
+                foreach ($search_types as $type) {
+                    if ($searchFor == $type) {
+                            ?>
+                            <option value='<?php echo str_replace(" ", "", $type); ?>' selected="selected"><?php echo $type; ?></option>
+                            <?php
+                    } else {
+                            ?>
+                            <option value='<?php echo str_replace(" ", "", $type); ?>'><?php echo $type; ?></option>
+                            <?php
+                    }
+                }
+                ?>
+                </select>
+            </label>
+            <input type="submit" value="Search!">
+        </p>
+    </form>
+<?php
+if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+    $amazon = new Zend_Service_Amazon_Query("1338XJTNFMTHK413WFR2");
+
+    try {
+        $amazon->category($searchFor)->ResponseGroup('Large')->Keywords($keywords);
+
+        $results = $amazon->search();
+
+        if ($results->totalResults() > 0) {
+            echo '<div id="results">';
+            echo '<h2>Search Results</h2>';
+            foreach ($results as $result) {
+                echo "<div>";
+                echo "<h3>$result->Title</h3>";
+                if (isset($result->MediumImage)) {
+                    ?>
+                        <div class="thumb">
+                            <a href='<?php echo $result->DetailPageURL; ?>' title='<?php echo $result->Title; ?>'>
+                                <img src='<?php echo $result->MediumImage->Url->getUri(); ?>' />
+                            </a>
+                        </div>
+                        <p class="details" style="height: <?php echo $result->MediumImage->Height; ?>px">
+                            Average Rating: <?php echo $result->AverageRating; ?>
+                            <br />
+                            Total Reviews: <?php echo $result->TotalReviews; ?>
+                            <br />
+                            Price: <?php echo (isset($result->FormattedPrice)) ? $result->FormattedPrice : "Not available"; ?>
+                            <br />
+                            <a href='<?php echo $result->DetailPageURL; ?>'>More Details...</a>
+                        </p>
+                    <?php
+                } else {
+                    echo "<a href='{$result->DetailPageURL}'>More Details...</a>";
+                }
+                echo "</div>";
+            }
+            echo '</div>';
+        }
+    }
+    catch (Zend_Service_Exception $e) {
+        echo '<p style="color: red; font-weight: bold">An error occured, please try again later. (' .$e->getMessage(). ')</p>';
+    }
+}
+?>
+<p id="poweredby" style="text-align: center; font-size: 9px;">Powered by the <a href="http://framework.zend.com">Zend Framework</a></p>
+</body>
+</html>

+ 162 - 0
demos/Zend/WebServices/Flickr/flickr-composite.php

@@ -0,0 +1,162 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Flickr
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Query Yahoo! Web, Image and News searches
+ */
+
+/**
+ * @see Zend_Service_Flickr
+ */
+require_once 'Zend/Service/Flickr.php';
+
+if (isset($_POST) && strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+    $keywords = strip_tags($_POST['search_term']);
+} else {
+    $keywords = '';
+}
+
+?>
+<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+    <style type="text/css">
+        html, body {
+            margin: 0px;
+            padding: 0px;
+            font-family: Tahoma, Verdana, sans-serif;
+            font-size: 10px;
+        }
+
+        h1 {
+            margin-top: 0px;
+            background-color: darkblue;
+            color: white;
+            font-size: 16px;
+        }
+
+        form {
+            text-align: center;
+        }
+
+        label {
+            font-weight: bold;
+        }
+
+        img {
+            border: 0px;
+            padding: 5px;
+        }
+
+        #composite {
+            text-align: center;
+            padding: 25px;
+            background-color: black;
+            margin-left: auto;
+            margin-right: auto;
+        }
+
+        h2 {
+            font-size: 14px;
+            color: white;
+            text-align: center;
+        }
+
+        #poweredby {
+            clear: both;
+        }
+    </style>
+</head>
+<body>
+    <h1>Flickr Compositor</h1>
+    <form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
+        <p>
+            <label>Search For: <input type="text" name="search_term" value="<?php echo $keywords; ?>"></label>
+            <input type="submit" value="Search!" onclick='this.value="Please Wait..."'>
+        </p>
+    </form>
+<?php
+if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+    $flickr = new Zend_Service_Flickr('381e601d332ab5ce9c25939570cb5c4b');
+
+    try {
+        $results = $flickr->tagSearch($keywords, array('per_page' => 50, 'tag_mode' => 'all'));
+
+        if ($results->totalResults() > 0) {
+            $images = array();
+            foreach ($results as $result) {
+                if (isset($result->Medium)) {
+                    $images[] = imagecreatefromjpeg($result->Medium->uri);
+                    $heights[] = $result->Medium->height;
+                    $widths[] = $result->Medium->width;
+                }
+            }
+            if (sizeof($images) == 0) {
+                echo '<p style="color: orange; font-weight: bold">No Results Found.</p>';
+            } else {
+                sort($heights);
+                sort($widths);
+                $max_height = array_pop($heights);
+                $max_width = array_pop($widths);
+                $output = realpath("./temp/") .DIRECTORY_SEPARATOR.mt_rand(). ".jpg";
+                foreach ($images as $key => $image) {
+                    if (!file_exists('./temp')) {
+                        mkdir("./temp");
+                    }
+
+                    $tmp = tempnam(realpath('./temp'), 'zflickr');
+                    imagejpeg($image, $tmp);
+
+                    chmod($tmp, 0777);
+
+                    if (file_exists($output)) {
+                        passthru("composite -dissolve 20 $tmp $output $output");
+                        chmod($output, 0777);
+                    } elseif (!isset($previous_image)) {
+                        $previous_image = "$tmp";
+                    } else {
+                        passthru("composite -dissolve 20 $tmp $previous_image $output");
+                        chmod($output, 0777);
+                    }
+                    $image_files[] = $tmp;
+                }
+                foreach ($image_files as $filename) {
+                    unlink($filename);
+                }
+                //copy($output, basename($output));
+                //unlink($output);
+                $size = getimagesize($output);
+                $size[0] += 25;
+                $size[1] += 25;
+                echo "<div id='composite' style='width: {$size[0]}px; height: {$size[1]}px;'><img src='temp/" .basename($output). "' alt='" .htmlspecialchars($keywords). "'><h2>" .ucwords(htmlspecialchars($keywords)). "</h2></div>";
+            }
+        } else {
+            echo '<p style="color: orange; font-weight: bold">No Results Found</p>';
+        }
+    }
+    catch (Zend_Service_Exception $e) {
+        echo '<p style="color: red; font-weight: bold">An error occured, please try again later. (' .$e->getMessage(). ')</p>';
+    }
+}
+?>
+<p id="poweredby" style="text-align: center; font-size: 9px;">Powered by the <a href="http://framework.zend.com">Zend Framework</a></p>
+</body>
+</html>

+ 41 - 0
demos/Zend/WebServices/Flickr/flickr-search.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Flickr
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Query Flickr for a tag and display all of the photos for
+ * that tag.
+ */
+
+error_reporting(E_ALL);
+
+/**
+ * @see Zend_Service_Flickr
+ */
+require_once 'Zend/Service/Flickr.php';
+
+$flickr = new Zend_Service_Flickr('your api key here');
+
+$photos = $flickr->tagSearch('php');
+
+foreach ($photos as $photo) {
+    echo '<img src="' . $photo->Thumbnail->uri . '" /> <br />';
+    echo $photo->title . "<br /> \n";
+}

+ 31 - 0
demos/Zend/WebServices/Protocols/xmlrpc-upc-lookup.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_XmlRpc
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * @see Zend_XmlRpc_Client
+ */
+require_once 'Zend/XmlRpc/Client.php';
+
+$server = new Zend_XmlRpc_Client('http://www.upcdatabase.com/rpc');
+
+$client = $server->getProxy();
+
+print_r( $client->lookupUPC('071641301627') );

+ 153 - 0
demos/Zend/WebServices/Yahoo/yahoo-multi-search.php

@@ -0,0 +1,153 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Service_Yahoo
+ * @subpackage Demos
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * Query Yahoo! Web, Image and News searches
+ */
+
+/**
+ * @see Zend_Service_Yahoo
+ */
+require_once 'Zend/Service/Yahoo.php';
+
+if (isset($_POST) && strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+        $keywords = strip_tags($_POST['search_term']);
+} else {
+    $keywords = '';
+}
+
+?>
+<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+    <style type="text/css">
+        html, body {
+            margin: 0px;
+            padding: 0px;
+            font-family: Tahoma, Verdana, sans-serif;
+            font-size: 10px;
+        }
+
+        h1 {
+            margin-top: 0px;
+            background-color: darkblue;
+            color: white;
+            font-size: 16px;
+        }
+
+        form {
+            text-align: center;
+        }
+
+        label {
+            font-weight: bold;
+        }
+
+        img {
+            border: 0px;
+            padding: 5px;
+        }
+
+        #web, #news {
+            float: left;
+            width: 48%;
+            margin-left: 10px;
+        }
+
+        #image {
+            margin: 10px;
+            border: 1px dashed grey;
+            background-color: lightgrey;
+            text-align: center;
+        }
+
+        h2 {
+            font-size: 14px;
+            color: grey;
+        }
+
+        h3 {
+            font-size: 12px;
+        }
+
+        #poweredby {
+            clear: both;
+        }
+    </style>
+</head>
+<body>
+    <h1>Yahoo! Multi-Search</h1>
+    <form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="post">
+        <p>
+            <label>Search For: <input type="text" name="search_term" value="<?php echo htmlspecialchars($keywords, ENT_QUOTES); ?>"></label>
+            <input type="submit" value="Search!">
+        </p>
+    </form>
+<?php
+    if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
+        $yahoo = new Zend_Service_Yahoo('zendtesting');
+
+        try {
+            $results = $yahoo->imageSearch($keywords, array("results" => 5));
+
+            if ($results->totalResults() > 0) {
+                echo '<div id="image">';
+                echo '<h2>Image Search Results</h2>';
+                foreach ($results as $result) {
+                    echo "<a href='{$result->ClickUrl}' title='$result->Title'><img src='{$result->Thumbnail->Url->getUri()}'></a>";
+                }
+                echo '</div>';
+            }
+
+
+            $results = $yahoo->webSearch($keywords);
+
+            if ($results->totalResults() > 0) {
+                echo '<div id="web">';
+                echo '<h2>Web Search Results</h2>';
+                foreach ($results as $result) {
+                    echo "<h3><a href='{$result->ClickUrl}'>{$result->Title}</a></h3>";
+                    echo "<p>{$result->Summary} <br> [<a href='{$result->CacheUrl}'>Cached Version</a>]</p>";
+                }
+                echo '</div>';
+            }
+
+
+            $results = $yahoo->newsSearch($keywords);
+
+            if ($results->totalResults() > 0) {
+                echo '<div id="news">';
+                echo '<h2>News Search Results</h2>';
+                foreach ($results as $result) {
+                    echo "<h3><a href='{$result->ClickUrl}'>{$result->Title}</a></h3>";
+                    echo "<p>{$result->Summary}</p>";
+                }
+                echo '</div>';
+            }
+        }
+        catch (Zend_Service_Exception $e) {
+            echo '<p style="color: red; font-weight: bold">An error occured, please try again later.</p>';
+        }
+    }
+?>
+<p id="poweredby" style="text-align: center; font-size: 9px;">Powered by the <a href="http://framework.zend.com">Zend Framework</a></p>
+</body>
+</html>

+ 18 - 0
demos/Zend/Wildfire/application/controllers/Boot/Zend-Db-Profiler-Firebug/IndexController.php

@@ -0,0 +1,18 @@
+<?php
+
+require_once 'Zend/Controller/Action.php';
+
+class IndexController extends Zend_Controller_Action
+{
+    public function indexAction()
+    {
+        $db = Zend_Registry::get('db');
+
+        $db->getConnection()->exec('CREATE TABLE foo (
+                                      id      INTEGNER NOT NULL,
+                                      col1    VARCHAR(10) NOT NULL  
+                                    )');
+
+        $db->insert('foo', array('id'=>1,'col1'=>'original'));
+    }
+}

+ 13 - 0
demos/Zend/Wildfire/application/controllers/Boot/Zend-Log-Writer-Firebug/IndexController.php

@@ -0,0 +1,13 @@
+<?php
+
+require_once 'Zend/Controller/Action.php';
+
+class IndexController extends Zend_Controller_Action
+{
+    public function indexAction()
+    {
+        $logger = Zend_Registry::get('logger');
+
+        $logger->log('This is a log message!', Zend_Log::INFO);      
+    }
+}

+ 51 - 0
demos/Zend/Wildfire/application/controllers/ErrorController.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/**
+ * A sample error controller.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ErrorController extends Zend_Controller_Action
+{
+    public function errorAction()
+    {
+        /*
+         * Make sure we don't log exceptions thrown during the exception logging.
+         * If we do we will create an infinite loop!
+         */
+
+        try {
+
+            Zend_Registry::get('logger')->err($this->_getParam('error_handler')->exception);
+          
+        } catch(Exception $e) {
+          
+          /* TODO: You can log this exception somewhere or display it during development.
+           *       DO NOT USE THE logger here as it will create an infinite loop!
+           */
+          
+        }
+    }
+}
+

+ 38 - 0
demos/Zend/Wildfire/application/controllers/IndexController.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Controller_Action */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * Index Controller
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class IndexController extends Zend_Controller_Action
+{
+    public function indexAction()
+    {
+    }
+}
+

+ 91 - 0
demos/Zend/Wildfire/application/controllers/ZendDbProfilerFirebugController.php

@@ -0,0 +1,91 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Controller_Action */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * Tests for Zend_Db_Profiler_Firebug
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ZendDbProfilerFirebugController extends Zend_Controller_Action
+{
+  
+    public function testloggingAction()
+    {
+        $db = Zend_Registry::get('db');
+
+        $db->getConnection()->exec('CREATE TABLE foo (
+                                      id      INTEGNER NOT NULL,
+                                      col1    VARCHAR(10) NOT NULL  
+                                    )');
+
+        $db->insert('foo', array('id'=>1,'col1'=>'original'));
+
+        $db->fetchAll('SELECT * FROM foo WHERE id = ?', 1);
+
+        $db->update('foo', array('col1'=>'new'), 'id = 1');
+
+        $db->fetchAll('SELECT * FROM foo WHERE id = ?', 1);
+
+        $db->delete('foo', 'id = 1');
+
+        $db->getConnection()->exec('DROP TABLE foo');
+    }
+    
+    public function testmultipledatabasesAction()
+    {
+        $profiler1 = new Zend_Db_Profiler_Firebug('All DB Queries for first database');
+        
+        $db1 = Zend_Db::factory('PDO_SQLITE',
+                               array('dbname' => ':memory:',
+                                     'profiler' => $profiler1));
+        
+        $db1->getProfiler()->setEnabled(true);
+
+        $profiler2 = new Zend_Db_Profiler_Firebug('All DB Queries for second database');
+        
+        $db2 = Zend_Db::factory('PDO_SQLITE',
+                               array('dbname' => ':memory:',
+                                     'profiler' => $profiler2));
+        
+        $db2->getProfiler()->setEnabled(true);
+        
+        $db1->getConnection()->exec('CREATE TABLE foo (
+                                      id      INTEGNER NOT NULL,
+                                      col1    VARCHAR(10) NOT NULL  
+                                    )');
+
+        $db1->insert('foo', array('id'=>1,'col1'=>'original'));
+        
+        $db2->getConnection()->exec('CREATE TABLE foo (
+                                      id      INTEGNER NOT NULL,
+                                      col1    VARCHAR(10) NOT NULL  
+                                    )');
+
+        $db2->insert('foo', array('id'=>1,'col1'=>'original'));
+    }
+
+}
+

+ 79 - 0
demos/Zend/Wildfire/application/controllers/ZendLogWriterFirebugController.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Controller_Action */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * Tests for Zend_Log_Writer_Firebug
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ZendLogWriterFirebugController extends Zend_Controller_Action
+{
+
+    public function testloggingAction()
+    {
+        $logger = Zend_Registry::get('logger');
+
+        $logger->log('Emergency: system is unusable',            Zend_Log::EMERG);
+        $logger->log('Alert: action must be taken immediately',  Zend_Log::ALERT);
+        $logger->log('Critical: critical conditions',            Zend_Log::CRIT);
+        $logger->log('Error: error conditions',                  Zend_Log::ERR);
+        $logger->log('Warning: warning conditions',              Zend_Log::WARN);
+        $logger->log('Notice: normal but significant condition', Zend_Log::NOTICE);
+        $logger->log('Informational: informational messages',    Zend_Log::INFO);
+        $logger->log('Debug: debug messages',                    Zend_Log::DEBUG);
+        $logger->log(array('$_SERVER',$_SERVER),                 Zend_Log::DEBUG);
+
+        $logger->trace('Trace to here');
+
+        $table = array('Summary line for the table',
+                       array(
+                           array('Column 1', 'Column 2'),
+                           array('Row 1 c 1',' Row 1 c 2'),
+                           array('Row 2 c 1',' Row 2 c 2')
+                       )
+                      );
+        $logger->table($table);
+    }
+
+
+    public function testerrorcontrollerAction()
+    {
+        require_once 'Zend/Exception.php';
+        throw new Zend_Exception('Test Exception');
+    }
+
+    public function testlargemessageAction()
+    {
+        $message = array();
+
+        for ( $i=0 ; $i<300 ; $i++ ) {
+            $message[] = 'This is message #' . $i;
+        }
+
+        $logger = Zend_Registry::get('logger');
+        $logger->log($message, Zend_Log::INFO);
+    }
+}

+ 44 - 0
demos/Zend/Wildfire/application/controllers/ZendWildfirePluginFirePhpController.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/** Zend_Controller_Action */
+require_once 'Zend/Controller/Action.php';
+
+/**
+ * Tests for Zend_Wildfire_Plugin_FirePhp
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class ZendWildfirePluginFirePhpController extends Zend_Controller_Action
+{
+    public function testgroupsAction()
+    {
+        Zend_Wildfire_Plugin_FirePhp::group('Group 1');
+        Zend_Wildfire_Plugin_FirePhp::send('Test Message 1');
+        Zend_Wildfire_Plugin_FirePhp::group('Group 2');
+        Zend_Wildfire_Plugin_FirePhp::send('Test Message 2', 'Label', Zend_Wildfire_Plugin_FirePhp::INFO);
+        Zend_Wildfire_Plugin_FirePhp::groupEnd();
+        Zend_Wildfire_Plugin_FirePhp::send('Test Message 3');
+        Zend_Wildfire_Plugin_FirePhp::groupEnd();
+    }
+}

+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/error/error.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+We had an error! But it's ok. We are testing the ErrorController.
+
+</body>
+</html>

+ 57 - 0
demos/Zend/Wildfire/application/views/scripts/index/index.phtml

@@ -0,0 +1,57 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+<table border="0" width="100%" height="100%" cellpadding="10" cellspacing="0">
+	<tr>
+		<td valign="top" nowrap>
+			<p><b>Zend_Log_Writer_Firebug</b></p>
+			<ul>
+				<li><a href="Zend-Log-Writer-Firebug/TestLogging" target="main">Test Logging</a></li>
+				<li><a href="Zend-Log-Writer-Firebug/TestErrorController" target="main">Test Error Controller</a></li>
+				<li><a href="Zend-Log-Writer-Firebug/TestLargeMessage" target="main">Test Large Message</a></li>
+				<li><a href="Boot/Zend-Log-Writer-Firebug/TestDocExample.php?Example=WithController" target="main">Test Doc Example (With Controller)</a></li>
+				<li><a href="Boot/Zend-Log-Writer-Firebug/TestDocExample.php?Example=WithoutController" target="main">Test Doc Example (Without Controller)</a></li>
+			</ul>
+			<p><b>Zend_Db_Profiler_Firebug</b></p>
+			<ul>
+				<li><a href="Zend-Db-Profiler-Firebug/TestLogging" target="main">Test Logging</a></li>
+				<li><a href="Zend-Db-Profiler-Firebug/TestMultipleDatabases" target="main">Test Multiple Databases</a></li>
+				<li><a href="Boot/Zend-Db-Profiler-Firebug/TestDocExample.php?Example=WithController" target="main">Test Doc Example (With Controller)</a></li>
+				<li><a href="Boot/Zend-Db-Profiler-Firebug/TestDocExample.php?Example=WithoutController" target="main">Test Doc Example (Without Controller)</a></li>
+			</ul>
+			<p><b>Zend_Wildfire_Plugin_FirePhp</b></p>
+			<ul>
+				<li><a href="Zend-Wildfire-Plugin-FirePhp/TestGroups" target="main">Test Groups</a></li>
+			</ul>
+			
+			<br/>
+			<br/>
+			
+			<p><b>Requirements</b></p>
+			<ul>
+				<li><a target="_blank" href="http://www.getfirefox.com/">Firefox 3</a></li>
+				<li><a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/1843">Firebug Extension</a></li>
+				<li><a target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/6149">FirePHP Extension</a></li>
+			</ul>
+
+			<p><b>Instructions</b></p>
+			<p>Click on the links above and<br>look for output in the Firebug Console.</p>
+			
+		</td>
+		<td valign="top" width="100%">
+			<iframe name="main" width="100%" height="100%" border="1"></iframe>
+		</td>
+	</tr>
+</table>
+
+</body>
+</html>

+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/zend-db-profiler-firebug/test-logging.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+Test Logging
+
+</body>
+</html>

+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/zend-db-profiler-firebug/test-multiple-databases.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+Test Multiple Databases
+
+</body>
+</html>

+ 0 - 0
demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-error-controller.phtml


+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-large-message.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+Test Large Message
+
+</body>
+</html>

+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/zend-log-writer-firebug/test-logging.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+Test Logging
+
+</body>
+</html>

+ 16 - 0
demos/Zend/Wildfire/application/views/scripts/zend-wildfire-plugin-firephp/test-groups.phtml

@@ -0,0 +1,16 @@
+<html>
+<head>
+	<title>Zend_Wildfire Demos and Tests</title>
+	<style>
+		BODY, TD {
+			font-family:verdana,arial,helvetica,sans-serif;
+			font-size:11px;		
+		}
+	</style>
+</head>
+<body>
+
+Test Groups
+
+</body>
+</html>

+ 6 - 0
demos/Zend/Wildfire/public/.htaccess

@@ -0,0 +1,6 @@
+
+RewriteEngine on
+
+RewriteRule Boot/(.*) Boot/$1 [QSA,L]
+
+RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php

+ 51 - 0
demos/Zend/Wildfire/public/Boot/Zend-Db-Profiler-Firebug/TestDocExample.php

@@ -0,0 +1,51 @@
+<?php
+
+require_once('Zend/Loader.php');
+Zend_Loader::registerAutoload();
+
+switch($_GET['Example']) {
+    
+    case 'WithController':
+  
+        $profiler = new Zend_Db_Profiler_Firebug('All DB Queries');
+        $profiler->setEnabled(true);
+        $db = Zend_Db::factory('PDO_SQLITE', array('dbname' => ':memory:'));
+        $db->setProfiler($profiler);
+        Zend_Registry::set('db',$db);
+        
+        $controller = Zend_Controller_Front::getInstance();
+        $controller->setParam('useDefaultControllerAlways',true);
+        $controller->setParam('noViewRenderer', true);        
+        $controller->setControllerDirectory(dirname(dirname(dirname(dirname(__FILE__)))).'/application/controllers/Boot/Zend-Db-Profiler-Firebug');
+        $controller->dispatch();
+  
+        print 'Test Doc Example with Controller';
+        break;
+  
+    case 'WithoutController':
+              
+        $profiler = new Zend_Db_Profiler_Firebug('All DB Queries');
+        $profiler->setEnabled(true);
+        $db = Zend_Db::factory('PDO_SQLITE', array('dbname' => ':memory:'));
+        $db->setProfiler($profiler);
+        
+        $request  = new Zend_Controller_Request_Http();
+        $response = new Zend_Controller_Response_Http();
+        $channel  = Zend_Wildfire_Channel_HttpHeaders::getInstance();
+        $channel->setRequest($request);
+        $channel->setResponse($response);
+
+        $db->getConnection()->exec('CREATE TABLE foo (
+                                      id      INTEGNER NOT NULL,
+                                      col1    VARCHAR(10) NOT NULL  
+                                    )');
+
+        $db->insert('foo', array('id'=>1,'col1'=>'original'));
+        
+        $channel->flush();
+        $response->sendHeaders();
+
+        print 'Test Doc Example without Controller';
+        break;
+  
+}

+ 41 - 0
demos/Zend/Wildfire/public/Boot/Zend-Log-Writer-Firebug/TestDocExample.php

@@ -0,0 +1,41 @@
+<?php
+
+require_once('Zend/Loader.php');
+Zend_Loader::registerAutoload();
+
+switch($_GET['Example']) {
+    
+    case 'WithController':
+    
+        $writer = new Zend_Log_Writer_Firebug();
+        $logger = new Zend_Log($writer);
+        Zend_Registry::set('logger',$logger);
+        
+        $controller = Zend_Controller_Front::getInstance();
+        $controller->setParam('useDefaultControllerAlways',true);
+        $controller->setParam('noViewRenderer', true);        
+        $controller->setControllerDirectory(dirname(dirname(dirname(dirname(__FILE__)))).'/application/controllers/Boot/Zend-Log-Writer-Firebug');
+        $controller->dispatch();
+        
+        print 'Test Doc Example with Controller';
+        break;
+  
+    case 'WithoutController':
+    
+        $writer = new Zend_Log_Writer_Firebug();
+        $logger = new Zend_Log($writer);
+        
+        $request = new Zend_Controller_Request_Http();
+        $response = new Zend_Controller_Response_Http();
+        $channel = Zend_Wildfire_Channel_HttpHeaders::getInstance();
+        $channel->setRequest($request);
+        $channel->setResponse($response);
+        
+        $logger->log('This is a log message!', Zend_Log::INFO);
+        
+        $channel->flush();
+        $response->sendHeaders();
+        
+        print 'Test Doc Example without Controller';
+        break;
+}

+ 66 - 0
demos/Zend/Wildfire/public/index.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Wildfire
+ * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+
+/* NOTE: You must have Zend Framework in your include path! */
+
+/*
+ * Add our Firebug Log Writer to the registry
+ */
+
+require_once 'Zend/Registry.php';
+require_once 'Zend/Log.php';
+require_once 'Zend/Log/Writer/Firebug.php';
+
+$writer = new Zend_Log_Writer_Firebug();
+$writer->setPriorityStyle(8, 'TABLE');
+$writer->setPriorityStyle(9, 'TRACE');
+
+$logger = new Zend_Log($writer);
+$logger->addPriority('TABLE', 8);
+$logger->addPriority('TRACE', 9);
+
+Zend_Registry::set('logger',$logger);
+
+
+/*
+ * Add our Firebug DB Profiler to the registry
+ */
+ 
+require_once 'Zend/Db.php';
+require_once 'Zend/Db/Profiler/Firebug.php';
+
+$profiler = new Zend_Db_Profiler_Firebug('All DB Queries');
+
+$db = Zend_Db::factory('PDO_SQLITE',
+                       array('dbname' => ':memory:',
+                             'profiler' => $profiler));
+
+$db->getProfiler()->setEnabled(true);
+
+Zend_Registry::set('db',$db);
+
+
+/*
+ * Run the front controller
+ */
+
+require_once 'Zend/Controller/Front.php';
+
+Zend_Controller_Front::run(dirname(dirname(__FILE__)).'/application/controllers');

部分文件因文件數量過多而無法顯示