# HG changeset patch
# User clebeaupin
# Date 1272467924 -7200
# Node ID 2fda205cc5dee96b60d73fe9d4eff7ff727e0321
# Parent 3189b5c66e3d369fd4096cec347f4ed86e4d9aea
Add plugins to scale images
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/apps/frontend/config/settings.yml
--- a/web/thdProject/apps/frontend/config/settings.yml Wed Apr 28 15:24:36 2010 +0200
+++ b/web/thdProject/apps/frontend/config/settings.yml Wed Apr 28 17:18:44 2010 +0200
@@ -27,7 +27,8 @@
# Output escaping settings
escaping_strategy: false # Determines how variables are made available to templates. Accepted values: on, off.
escaping_method: ESC_SPECIALCHARS # Function or helper used for escaping. Accepted values: ESC_RAW, ESC_ENTITIES, ESC_JS, ESC_JS_NO_ENTITIES, and ESC_SPECIALCHARS.
- standard_helpers: [Partial, Cache, Form, ThdHtml]
+ standard_helpers: [Partial, Cache, Form, ThdHtml, UcImageScaler]
+ enabled_modules: [default, UcImageScaler]
login_module: account
login_action: loginUser
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/apps/frontend/modules/homepage/templates/_randomFilm.php
--- a/web/thdProject/apps/frontend/modules/homepage/templates/_randomFilm.php Wed Apr 28 15:24:36 2010 +0200
+++ b/web/thdProject/apps/frontend/modules/homepage/templates/_randomFilm.php Wed Apr 28 17:18:44 2010 +0200
@@ -72,7 +72,7 @@
function playerLoaded(player) {
// Charge les tags
-
+
tagTool.player = $f("player");
tagTool.showTagInPage = false;
@@ -81,7 +81,7 @@
Regardez et annotez des extraits :
-
+
@@ -91,18 +91,18 @@
Tags liés au film :
- getTagsArray() as $item) {
- if($item){
+ if($item){
$tag = $item;
echo '
'.$tag.'
';
-
+
}
} ?>
-
+
-
+
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/config/config.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/config/config.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,4 @@
+dispatcher->connect('routing.load_configuration', array('UcImageScalerEventListener', 'listenToRoutingLoadConfigurationEvent'));
+
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageCache.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageCache.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,7 @@
+getFilePath($key));
+ }
+}
\ No newline at end of file
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageCacheManager.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageCacheManager.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,83 @@
+loadBaseConfiguration();
+ }
+ /**
+ * Load the base configuration
+ */
+ public function loadBaseConfiguration() {
+ $this->cache = new UcImageCache(array('cache_dir' => sfConfig::get('sf_app_cache_dir').'/uc_image'));
+ }
+
+ public static function createImageIdFor($imageUrl) {
+ return md5($imageUrl);
+ }
+
+ public static function getInstance() {
+ if(is_null(self::$instance)) {
+ self::$instance = new UcImageCacheManager();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * return cached image id
+ *
+ * @param string $urlImage
+ * @param int $mw
+ * @param int $mh
+ * @return string
+ */
+ public function getOrCreatedCachedImageId($imageUrl, $mw, $mh, $useTransparency=false, $keepRatio=true) {
+ $imageId = self::createImageIdFor($imageUrl);
+ $imageKey = $imageId.'-'.$mw.'x'.$mh;
+
+ //not already cached
+ if(!$this->cache->has($imageKey)) {
+ $thumbnail = new sfThumbnail($mw, $mh, $keepRatio, true, 80, 'sfImageMagickAdapter');
+
+ try {
+ $thumbnail->loadFile($imageUrl);
+ } catch (Exception $e) {
+ // Image could not be loaded. Use a default one
+ sfContext::getInstance() -> getLogger() -> warning( "Unable to create cached image of ".$imageUrl." for the following reason : ".
+ $e->getMessage() .". 404 image will be used");
+
+ if ($mw > $mh) {
+ $imagePath = sfConfig::get('app_image_cache_landscape_image_path');
+ } else {
+ $imagePath = sfConfig::get('app_image_cache_portrait_image_path');
+ }
+
+ $thumbnail->loadFile($imagePath);
+ }
+
+ $contentType = ($useTransparency) ? null : 'image/jpeg';
+ $this->cache->set($imageKey, $thumbnail->toString($contentType));
+
+ // Free all ressources
+ $thumbnail->freeSource();
+ $thumbnail->freeAll();
+ }
+
+ return $imageId;
+ }
+
+ public function getImage($imageKey) {
+ return $this->cache->get($imageKey);
+ }
+
+ public function getImageMd5($imageKey) {
+ return $this->cache->getImageMd5($imageKey);
+ }
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageScalerEventListener.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageScalerEventListener.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,16 @@
+getSubject();
+ if( !$r -> hasRouteName( 'UcImageScalerCachedImageUrl') )
+ {
+ $r->prependRoute('UcImageScalerCachedImageUrl',
+ new sfRoute('/dynimage/:mw/:mh/:fid/*',
+ array('module' => 'UcImageScaler', 'action' => 'ImageDisplay')
+ ));
+ }
+ }
+}
\ No newline at end of file
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageUtil.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/lib/UcImageUtil.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,297 @@
+ getOrCreatedCachedImageId($urlImage, $mw, $mh , $useTransparency, $keepRatio);
+ $imageUrl = sfConfig::get('app_image_cache_host').url_for("@UcImageScalerCachedImageUrl?mw=$mw&mh=$mh&fid=$imgid&fname=$filename");
+ return $imageUrl;
+}
+/**
+ *
+ * return an image tag
+ * Sample :
+
+
+
+
+
+ * @param string $urlImage
+ * @param int $mw
+ * @param int $mh
+ * @param string $filename
+ * @param array $htmlattributes
+ * @return string
+ */
+function uc_imgtag_scimage( $urlImage, $mw, $mh, $useTransparency=false, $filename = '', $htmlattributes = Array() )
+{
+ $url = uc_url_for_scimage( $urlImage, $mw, $mh, $useTransparency, $filename );
+ $attrs = '';
+
+ foreach( $htmlattributes as $name=>$value )
+ {
+ $attrs = ' '.$name.'="'.$value.'"';
+ }
+ return '
';
+}
\ No newline at end of file
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/modules/UcImageScaler/actions/ImageDisplayAction.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/modules/UcImageScaler/actions/ImageDisplayAction.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,47 @@
+getParameter('fid');
+ $mw = $request->getParameter('mw');
+ $mh = $request->getParameter('mh');
+ $imageKey = $fid.'-'.$mw.'x'.$mh;
+
+ // Initialize response headers
+ $response = $this->getResponse();
+ $manager = UcImageCacheManager::getInstance();
+ $etag = $manager->getImageMd5($imageKey);
+
+ // Check etag
+ if ($request->getHttpHeader('IF_NONE_MATCH') == $etag) {
+ $response->setStatusCode(304);
+ $response->setHeaderOnly(true);
+ return sfView::NONE;
+ }
+
+ // Send resource
+ $uri = $request->getUri();
+ $ext = substr($uri, strrpos($uri, ".")+1);
+ $contentType = 'image/jpeg';
+ if ($ext == 'png') $contentType = 'image/png';
+ $response->clearHttpHeaders();
+ $response->setHttpHeader("Pragma", "public");
+ $response->setHttpHeader("ETag", $etag);
+ $response->setHttpHeader("Content-Type", $contentType);
+
+ // Set response content
+ $response->setContent($manager->getImage($imageKey));
+ return sfView::NONE;
+ }
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/UcImageScalerPlugin/modules/UcImageScaler/config/view.yml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/UcImageScalerPlugin/modules/UcImageScaler/config/view.yml Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,2 @@
+all:
+ has_layout: false
\ No newline at end of file
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfThumbnailPlugin/LICENSE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfThumbnailPlugin/LICENSE Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,7 @@
+Copyright (c) 2004-2006 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfThumbnailPlugin/README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfThumbnailPlugin/README Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,203 @@
+= sfThumbnailPlugin plugin =
+
+The `sfThumbnailPlugin` creates thumbnails from images. It relies on your
+choice of the [http://php.net/gd/ GD] or [http://www.imagemagick.org
+ImageMagick] libraries.
+
+== Installation ==
+
+To install the plugin for a symfony project, the usual process is to use the
+symfony command line.
+
+With symfony 1.0, use:
+
+{{{
+$ symfony plugin-install http://plugins.symfony-project.com/sfThumbnailPlugin
+}}}
+
+With symfony 1.1/1.2, use:
+
+{{{
+$ symfony plugin:install sfThumbnailPlugin
+}}}
+
+Alternatively, if you don't have PEAR installed, you can download the latest
+package attached to this plugin's wiki page and extract it under your project's
+`plugins/` directory.
+
+Clear the cache to enable the autoloading to find the new classes:
+{{{
+$ php symfony cc
+}}}
+
+You're done.
+
+'''Note''': If the [http://php.net/gd GD library] is not activated, you might
+have to uncomment the related line in your `php.ini` and restart your web
+server to enable PHP image handling functions.
+
+'''Note''': To use !ImageMagick, you'll need to download and install the
+binaries from http://www.imagemagick.org.
+
+== Contents ==
+
+The plugin contains three classes, `sfThumbnail`, `sfGDAdapter` and
+`sfImageMagickAdapter`. Available methods are:
+
+{{{
+// Initialize the thumbnail attributes
+__construct($maxWidth = null, $maxHeight = null, $scale = true, $inflate = true, $quality = 75, $adapterClass = null, $adapterOptions = array())
+
+// Load image file from a file system
+loadFile($imageFile)
+
+// Load image file from a string (GD adapter only, currently)
+loadData($imageString, $mimeType)
+
+// Save the thumbnail to a file
+save($thumbFile, $targetMime = null)
+}}}
+
+Supported GD image types are 'image/jpeg', 'image/png' and 'image/gif'.
+
+!ImageMagick supports over [http://www.imagemagick.org/script/formats.php 100
+types].
+
+Note that the $quality setting only applies to JPEG images.
+
+== Usage ==
+
+=== Creating a thumbnail from an existing image file ===
+
+The process of creating a thumbnail from an existing image file is pretty
+straightforward.
+
+First, you must initialize a new `sfThumbnail` object with two parameters: the
+maximum width and height of the desired thumbnail.
+
+{{{
+// Initialize the object for 150x150 thumbnails
+$thumbnail = new sfThumbnail(150, 150);
+}}}
+
+Then, specify a file path to the image to reduce to the `loadFile()` method.
+
+{{{
+// Load the image to reduce
+$thumbnail->loadFile('/path/to/image/file.png');
+}}}
+
+Finally, ask the thumbnail object to save the thumbnail. You must provide a
+file path. Optionally, if you don't want the thumbnail to use the same mime
+type as the source image, you can specify a mime type as the second parameter.
+
+{{{
+// Save the thumbnail
+$thumbnail->save('/path/to/thumbnail/file.jpg', 'image/jpg');
+}}}
+
+Both the source and destination file paths must be absolute paths in your
+filesystem. To store files under a symfony project directory, make sure you use
+the
+[http://www.symfony-project.com/book/trunk/19-Mastering-Symfony-s-Configuration-Files#The%20Basic%20File%20Structure
+directory constants], accessed by `sfConfig::get()`.
+
+=== Creating a thumbnail for an uploaded file ===
+
+If you upload images, you might need to create thumbnails of each uploaded file. For instance, to save a thumbnail of maximum size 150x150px at the same time as the uploaded image, the form handling action can look like:
+
+{{{
+public function executeUpload()
+{
+ // Retrieve the name of the uploaded file
+ $fileName = $this->getRequest()->getFileName('file');
+
+ // Create the thumbnail
+ $thumbnail = new sfThumbnail(150, 150);
+ $thumbnail->loadFile($this->getRequest()->getFilePath('file'));
+ $thumbnail->save(sfConfig::get('sf_upload_dir').'/thumbnail/'.$fileName, 'image/png');
+
+ // Move the uploaded file to the 'uploads' directory
+ $this->getRequest()->moveFile('file', sfConfig::get('sf_upload_dir').'/'.$fileName);
+
+ // Do whatever is next
+ $this->redirect('media/show?filename='.$fileName);
+}
+}}}
+
+Don't forget to create the `uploads/thumbnail/` directory before calling the action.
+
+== !ImageMagick-Specific Usage ==
+
+Usage is the same as above except you need to explicitly call the sfImageMagickAdapter class.
+
+{{{
+$thumbnail = new sfThumbnail(150, 150, true, true, 75, 'sfImageMagickAdapter');
+}}}
+
+=== Custom Options ===
+
+The last option in the constructor is an array that you can use to pass custom
+options to !ImageMagick. Below are some examples of this functionality.
+
+Extract the first page from a PDF document:
+
+{{{
+$thumbnail = new sfThumbnail(150, 150, true, true, 75, 'sfImageMagickAdapter', array('extract' => 1));
+}}}
+
+"1" stands for the first page, "2" for the second page, etc.
+
+If for some reason you use a non-standard name for your !ImageMagick binary, you can specify it like so:
+
+{{{
+$thumbnail = new sfThumbnail(150, 150, true, true, 75, 'sfImageMagickAdapter', array('convert' => 'my_imagemagick_binary'));
+}}}
+
+By default sfThumbnail resizes the image in order to get to the desired width
+and height of the thumbnail. But what if you want to force the thumbnail to be
+a certain width and height but without distorting the image of the source size
+scale is different than the thumbnail size scale.
+
+Now you can use a custom option "method" to achieve just that but "shaving" the
+source image in order to get it to be the same scale as the thumbnail and them
+resize it to the required dimentions.
+
+When "shave_bottom" is used and the image’s width is greater than the
+height then sfThumbnail shaves from both left side and right side until the
+desired scale.
+
+There is one requirement for the "method" option to work as expected and it is
+to turn off scaling (set the third parameter of sfThumbnail to FALSE).
+
+{{{
+$thumbnail = new sfThumbnail(150, 150, false, true, 75, 'sfImageMagickAdapter', array('method' => 'shave_all'));
+
+$thumbnail->loadFile('http://www.walkerbooks.co.uk/assets_walker/dynamic/1172005677146.png');
+$thumbnail->save('/tmp/shave.png', 'image/png');
+}}}
+
+== Changelog ==
+
+=== Trunk ===
+
+ * fabien: added a toResource() method to get the image resource for the thumbnail
+
+=== 2007-09-15 | 1.5.0 Stable ===
+
+ * kupokomapa: toString($mime) method now works for both adapters
+ * kupokomapa: loadFile() method can now accept a URI if sfWebBrowserPluging is available
+ * kupokomapa: Implemented "method" option for sfImageMagickAdapter where "method" for now can be "shave_all" or "shave_bottom"
+
+=== 2007-09-12 | 1.4.0 Stable ===
+
+ * davedash: Added toString($mime) function to save file to a string
+
+=== 2007-05-18 | 1.3.0 Stable ===
+
+ * bmeynell: Updated README
+ * bmeynell: Fixed ticket (#1710)
+ * bmeynell: Added adapter support
+ * francois: Updated README
+
+=== 2006-11-29 | 1.2.0 Stable ===
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfThumbnailPlugin/lib/sfGDAdapter.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfThumbnailPlugin/lib/sfGDAdapter.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,213 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfGDAdapter provides a mechanism for creating thumbnail images.
+ * @see http://www.php.net/gd
+ *
+ * @package sfThumbnailPlugin
+ * @author Fabien Potencier
+ * @author Benjamin Meynell
+ */
+
+class sfGDAdapter
+{
+
+ protected
+ $sourceWidth,
+ $sourceHeight,
+ $sourceMime,
+ $maxWidth,
+ $maxHeight,
+ $scale,
+ $inflate,
+ $quality,
+ $source,
+ $thumb;
+
+ /**
+ * List of accepted image types based on MIME
+ * descriptions that this adapter supports
+ */
+ protected $imgTypes = array(
+ 'image/jpeg',
+ 'image/pjpeg',
+ 'image/png',
+ 'image/gif',
+ );
+
+ /**
+ * Stores function names for each image type
+ */
+ protected $imgLoaders = array(
+ 'image/jpeg' => 'imagecreatefromjpeg',
+ 'image/pjpeg' => 'imagecreatefromjpeg',
+ 'image/png' => 'imagecreatefrompng',
+ 'image/gif' => 'imagecreatefromgif',
+ );
+
+ /**
+ * Stores function names for each image type
+ */
+ protected $imgCreators = array(
+ 'image/jpeg' => 'imagejpeg',
+ 'image/pjpeg' => 'imagejpeg',
+ 'image/png' => 'imagepng',
+ 'image/gif' => 'imagegif',
+ );
+
+ public function __construct($maxWidth, $maxHeight, $scale, $inflate, $quality, $options)
+ {
+ if (!extension_loaded('gd'))
+ {
+ throw new Exception ('GD not enabled. Check your php.ini file.');
+ }
+ $this->maxWidth = $maxWidth;
+ $this->maxHeight = $maxHeight;
+ $this->scale = $scale;
+ $this->inflate = $inflate;
+ $this->quality = $quality;
+ $this->options = $options;
+ }
+
+ public function loadFile($thumbnail, $image)
+ {
+ $imgData = @GetImageSize($image);
+
+ if (!$imgData)
+ {
+ throw new Exception(sprintf('Could not load image %s', $image));
+ }
+
+ if (in_array($imgData['mime'], $this->imgTypes))
+ {
+ $loader = $this->imgLoaders[$imgData['mime']];
+ if(!function_exists($loader))
+ {
+ throw new Exception(sprintf('Function %s not available. Please enable the GD extension.', $loader));
+ }
+
+ $this->source = $loader($image);
+ $this->sourceWidth = $imgData[0];
+ $this->sourceHeight = $imgData[1];
+ $this->sourceMime = $imgData['mime'];
+ $thumbnail->initThumb($this->sourceWidth, $this->sourceHeight, $this->maxWidth, $this->maxHeight, $this->scale, $this->inflate);
+
+ $this->thumb = imagecreatetruecolor($thumbnail->getThumbWidth(), $thumbnail->getThumbHeight());
+ if ($imgData[0] == $this->maxWidth && $imgData[1] == $this->maxHeight)
+ {
+ $this->thumb = $this->source;
+ }
+ else
+ {
+ imagecopyresampled($this->thumb, $this->source, 0, 0, 0, 0, $thumbnail->getThumbWidth(), $thumbnail->getThumbHeight(), $imgData[0], $imgData[1]);
+ }
+
+ return true;
+ }
+ else
+ {
+ throw new Exception(sprintf('Image MIME type %s not supported', $imgData['mime']));
+ }
+ }
+
+ public function loadData($thumbnail, $image, $mime)
+ {
+ if (in_array($mime,$this->imgTypes))
+ {
+ $this->source = imagecreatefromstring($image);
+ $this->sourceWidth = imagesx($this->source);
+ $this->sourceHeight = imagesy($this->source);
+ $this->sourceMime = $mime;
+ $thumbnail->initThumb($this->sourceWidth, $this->sourceHeight, $this->maxWidth, $this->maxHeight, $this->scale, $this->inflate);
+
+ $this->thumb = imagecreatetruecolor($thumbnail->getThumbWidth(), $thumbnail->getThumbHeight());
+ if ($this->sourceWidth == $this->maxWidth && $this->sourceHeight == $this->maxHeight)
+ {
+ $this->thumb = $this->source;
+ }
+ else
+ {
+ imagecopyresampled($this->thumb, $this->source, 0, 0, 0, 0, $thumbnail->getThumbWidth(), $thumbnail->getThumbHeight(), $this->sourceWidth, $this->sourceHeight);
+ }
+
+ return true;
+ }
+ else
+ {
+ throw new Exception(sprintf('Image MIME type %s not supported', $mime));
+ }
+ }
+
+ public function save($thumbnail, $thumbDest, $targetMime = null)
+ {
+ if($targetMime !== null)
+ {
+ $creator = $this->imgCreators[$targetMime];
+ }
+ else
+ {
+ $creator = $this->imgCreators[$thumbnail->getMime()];
+ }
+
+ if ($creator == 'imagejpeg')
+ {
+ imagejpeg($this->thumb, $thumbDest, $this->quality);
+ }
+ else
+ {
+ $creator($this->thumb, $thumbDest);
+ }
+ }
+
+ public function toString($thumbnail, $targetMime = null)
+ {
+ if ($targetMime !== null)
+ {
+ $creator = $this->imgCreators[$targetMime];
+ }
+ else
+ {
+ $creator = $this->imgCreators[$thumbnail->getMime()];
+ }
+
+ ob_start();
+ $creator($this->thumb);
+
+ return ob_get_clean();
+ }
+
+ public function toResource()
+ {
+ return $this->thumb;
+ }
+
+ public function freeSource()
+ {
+ if (is_resource($this->source))
+ {
+ imagedestroy($this->source);
+ }
+ }
+
+ public function freeThumb()
+ {
+ if (is_resource($this->thumb))
+ {
+ imagedestroy($this->thumb);
+ }
+ }
+
+ public function getSourceMime()
+ {
+ return $this->sourceMime;
+ }
+
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfThumbnailPlugin/lib/sfImageMagickAdapter.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfThumbnailPlugin/lib/sfImageMagickAdapter.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,381 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfImageMagickAdapter provides a mechanism for creating thumbnail images.
+ * @see http://www.imagemagick.org
+ *
+ * @package sfThumbnailPlugin
+ * @author Fabien Potencier
+ * @author Benjamin Meynell
+ */
+
+class sfImageMagickAdapter
+{
+
+ protected
+ $sourceWidth,
+ $sourceHeight,
+ $sourceMime,
+ $maxWidth,
+ $maxHeight,
+ $scale,
+ $inflate,
+ $quality,
+ $source,
+ $magickCommands;
+
+ /**
+ * Mime types this adapter supports
+ */
+ protected $imgTypes = array(
+ 'application/pdf',
+ 'application/postscript',
+ 'application/vnd.palm',
+ 'application/x-icb',
+ 'application/x-mif',
+ 'image/dcx',
+ 'image/g3fax',
+ 'image/gif',
+ 'image/jng',
+ 'image/jpeg',
+ 'image/pbm',
+ 'image/pcd',
+ 'image/pict',
+ 'image/pjpeg',
+ 'image/png',
+ 'image/ras',
+ 'image/sgi',
+ 'image/svg',
+ 'image/tga',
+ 'image/tiff',
+ 'image/vda',
+ 'image/vnd.wap.wbmp',
+ 'image/vst',
+ 'image/x-fits',
+ 'image/x-ms-bmp',
+ 'image/x-otb',
+ 'image/x-palm',
+ 'image/x-pcx',
+ 'image/x-pgm',
+ 'image/x-photoshop',
+ 'image/x-ppm',
+ 'image/x-ptiff',
+ 'image/x-viff',
+ 'image/x-win-bitmap',
+ 'image/x-xbitmap',
+ 'image/x-xv',
+ 'image/xpm',
+ 'image/xwd',
+ 'text/plain',
+ 'video/mng',
+ 'video/mpeg',
+ 'video/mpeg2',
+ );
+
+ /**
+ * Imagemagick-specific Type to Mime type map
+ */
+ protected $mimeMap = array(
+ 'bmp' => 'image/bmp',
+ 'bmp2' => 'image/bmp',
+ 'bmp3' => 'image/bmp',
+ 'cur' => 'image/x-win-bitmap',
+ 'dcx' => 'image/dcx',
+ 'epdf' => 'application/pdf',
+ 'epi' => 'application/postscript',
+ 'eps' => 'application/postscript',
+ 'eps2' => 'application/postscript',
+ 'eps3' => 'application/postscript',
+ 'epsf' => 'application/postscript',
+ 'epsi' => 'application/postscript',
+ 'ept' => 'application/postscript',
+ 'ept2' => 'application/postscript',
+ 'ept3' => 'application/postscript',
+ 'fax' => 'image/g3fax',
+ 'fits' => 'image/x-fits',
+ 'g3' => 'image/g3fax',
+ 'gif' => 'image/gif',
+ 'gif87' => 'image/gif',
+ 'icb' => 'application/x-icb',
+ 'ico' => 'image/x-win-bitmap',
+ 'icon' => 'image/x-win-bitmap',
+ 'jng' => 'image/jng',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'm2v' => 'video/mpeg2',
+ 'miff' => 'application/x-mif',
+ 'mng' => 'video/mng',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'otb' => 'image/x-otb',
+ 'p7' => 'image/x-xv',
+ 'palm' => 'image/x-palm',
+ 'pbm' => 'image/pbm',
+ 'pcd' => 'image/pcd',
+ 'pcds' => 'image/pcd',
+ 'pcl' => 'application/pcl',
+ 'pct' => 'image/pict',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/vnd.palm',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-pgm',
+ 'picon' => 'image/xpm',
+ 'pict' => 'image/pict',
+ 'pjpeg' => 'image/pjpeg',
+ 'png' => 'image/png',
+ 'png24' => 'image/png',
+ 'png32' => 'image/png',
+ );
+
+ public function __construct($maxWidth, $maxHeight, $scale, $inflate, $quality, $options)
+ {
+ $this->magickCommands = array();
+ $this->magickCommands['convert'] = isset($options['convert']) ? escapeshellcmd($options['convert']) : 'convert';
+ $this->magickCommands['identify'] = isset($options['identify']) ? escapeshellcmd($options['identify']) : 'identify';
+
+ exec($this->magickCommands['convert'], $stdout);
+ if (strpos($stdout[0], 'ImageMagick') === false)
+ {
+ throw new Exception(sprintf("ImageMagick convert command not found"));
+ }
+
+ exec($this->magickCommands['identify'], $stdout);
+ if (strpos($stdout[0], 'ImageMagick') === false)
+ {
+ throw new Exception(sprintf("ImageMagick identify command not found"));
+ }
+
+ $this->maxWidth = $maxWidth;
+ $this->maxHeight = $maxHeight;
+ $this->scale = $scale;
+ $this->inflate = $inflate;
+ $this->quality = $quality;
+ $this->options = $options;
+ }
+
+ public function toString($thumbnail, $targetMime = null)
+ {
+ ob_start();
+ $this->save($thumbnail, null, $targetMime);
+
+ return ob_get_clean();
+ }
+
+ public function toResource()
+ {
+ throw new Exception('The ImageMagick adapter does not support the toResource method.');
+ }
+
+ public function loadFile($thumbnail, $image)
+ {
+ // try and use getimagesize()
+ // on failure, use identify instead
+ $imgData = @getimagesize($image);
+ if (!$imgData)
+ {
+ exec($this->magickCommands['identify'].' '.escapeshellarg($image), $stdout, $retval);
+ if ($retval === 1)
+ {
+ throw new Exception('Image could not be identified.');
+ }
+ else
+ {
+ // get image data via identify
+ list($img, $type, $dimen) = explode(' ', $stdout[0]);
+ list($width, $height) = explode('x', $dimen);
+
+ $this->sourceWidth = $width;
+ $this->sourceHeight = $height;
+ $this->sourceMime = $this->mimeMap[strtolower($type)];
+ }
+ }
+ else
+ {
+ // use image data from getimagesize()
+ $this->sourceWidth = $imgData[0];
+ $this->sourceHeight = $imgData[1];
+ $this->sourceMime = $imgData['mime'];
+ }
+ $this->image = $image;
+
+ // open file resource
+ $source = fopen($image, 'r');
+
+ $this->source = $source;
+
+ $thumbnail->initThumb($this->sourceWidth, $this->sourceHeight, $this->maxWidth, $this->maxHeight, $this->scale, $this->inflate);
+
+ return true;
+ }
+
+ public function loadData($thumbnail, $image, $mime)
+ {
+ throw new Exception('This function is not yet implemented. Try a different adapter.');
+ }
+
+ public function save($thumbnail, $thumbDest, $targetMime = null)
+ {
+ $command = '';
+
+ $width = $this->sourceWidth;
+ $height = $this->sourceHeight;
+ $x = $y = 0;
+ switch (@$this->options['method'])
+ {
+ case "shave_all":
+ $proportion['source'] = $width / $height;
+ $proportion['thumb'] = $thumbnail->getThumbWidth() / $thumbnail->getThumbHeight();
+
+ if ($proportion['source'] > 1 && $proportion['thumb'] < 1)
+ {
+ $x = ($width - $height * $proportion['thumb']) / 2;
+ }
+ else
+ {
+ if ($proportion['source'] > $proportion['thumb'])
+ {
+ $x = ($width - $height * $proportion['thumb']) / 2;
+ }
+ else
+ {
+ $y = ($height - $width / $proportion['thumb']) / 2;
+ }
+ }
+
+ $command = sprintf(" -shave %dx%d", $x, $y);
+ break;
+
+ case "shave_bottom":
+ if ($width > $height)
+ {
+ $x = ceil(($width - $height) / 2 );
+ $width = $height;
+ }
+ elseif ($height > $width)
+ {
+ $y = 0;
+ $height = $width;
+ }
+
+ if (is_null($thumbDest))
+ {
+ $command = sprintf(
+ " -crop %dx%d+%d+%d %s '-' | %s",
+ $width, $height,
+ $x, $y,
+ escapeshellarg($this->image),
+ $this->magickCommands['convert']
+ );
+
+ $this->image = '-';
+ }
+ else
+ {
+ $command = sprintf(
+ " -crop %dx%d+%d+%d %s %s && %s",
+ $width, $height,
+ $x, $y,
+ escapeshellarg($this->image), escapeshellarg($thumbDest),
+ $this->magickCommands['convert']
+ );
+
+ $this->image = $thumbDest;
+ }
+
+ break;
+ case 'custom':
+ $coords = $this->options['coords'];
+ if (empty($coords)) break;
+
+ $x = $coords['x1'];
+ $y = $coords['y1'];
+ $width = $coords['x2'] - $coords['x1'];
+ $height = $coords['y2'] - $coords['y1'];
+
+ if (is_null($thumbDest))
+ {
+ $command = sprintf(
+ " -crop %dx%d+%d+%d %s '-' | %s",
+ $width, $height,
+ $x, $y,
+ escapeshellarg($this->image),
+ $this->magickCommands['convert']
+ );
+
+ $this->image = '-';
+ }
+ else
+ {
+ $command = sprintf(
+ " -crop %dx%d+%d+%d %s %s && %s",
+ $width, $height,
+ $x, $y,
+ escapeshellarg($this->image), escapeshellarg($thumbDest),
+ $this->magickCommands['convert']
+ );
+
+ $this->image = $thumbDest;
+ }
+ break;
+ } // end switch
+
+ $command .= ' -thumbnail ';
+ $command .= $thumbnail->getThumbWidth().'x'.$thumbnail->getThumbHeight();
+
+ // absolute sizing
+ if (!$this->scale)
+ {
+ $command .= '!';
+ }
+
+ if ($this->quality && $targetMime == 'image/jpeg')
+ {
+ $command .= ' -quality '.$this->quality.'% ';
+ }
+
+ // extract images such as pages from a pdf doc
+ $extract = '';
+ if (isset($this->options['extract']) && is_int($this->options['extract']))
+ {
+ if ($this->options['extract'] > 0)
+ {
+ $this->options['extract']--;
+ }
+ $extract = '['.escapeshellarg($this->options['extract']).'] ';
+ }
+
+ $output = (is_null($thumbDest))?'-':$thumbDest;
+ $output = (($mime = array_search($targetMime, $this->mimeMap))?$mime.':':'').$output;
+
+ $cmd = $this->magickCommands['convert'].' '.$command.' '.escapeshellarg($this->image).$extract.' '.escapeshellarg($output);
+
+ (is_null($thumbDest))?passthru($cmd):exec($cmd);
+ }
+
+ public function freeSource()
+ {
+ if (is_resource($this->source))
+ {
+ fclose($this->source);
+ }
+ }
+
+ public function freeThumb()
+ {
+ return true;
+ }
+
+ public function getSourceMime()
+ {
+ return $this->sourceMime;
+ }
+
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfThumbnailPlugin/lib/sfThumbnail.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfThumbnailPlugin/lib/sfThumbnail.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,264 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfThumbnail provides a mechanism for creating thumbnail images.
+ *
+ * This is taken from Harry Fueck's Thumbnail class and
+ * converted for PHP5 strict compliance for use with symfony.
+ *
+ * @package sfThumbnailPlugin
+ * @author Fabien Potencier
+ * @author Benjamin Meynell
+ */
+class sfThumbnail
+{
+ /**
+ * Width of thumbnail in pixels
+ */
+ protected $thumbWidth;
+
+ /**
+ * Height of thumbnail in pixels
+ */
+ protected $thumbHeight;
+
+ /**
+ * Temporary file if the source is not local
+ */
+ protected $tempFile = null;
+
+ /**
+ * Thumbnail constructor
+ *
+ * @param int (optional) max width of thumbnail
+ * @param int (optional) max height of thumbnail
+ * @param boolean (optional) if true image scales
+ * @param boolean (optional) if true inflate small images
+ * @param string (optional) adapter class name
+ * @param array (optional) adapter options
+ */
+ public function __construct($maxWidth = null, $maxHeight = null, $scale = true, $inflate = true, $quality = 75, $adapterClass = null, $adapterOptions = array())
+ {
+ if (!$adapterClass)
+ {
+ if (extension_loaded('gd'))
+ {
+ $adapterClass = 'sfGDAdapter';
+ }
+ else
+ {
+ $adapterClass = 'sfImageMagickAdapter';
+ }
+ }
+ $this->adapter = new $adapterClass($maxWidth, $maxHeight, $scale, $inflate, $quality, $adapterOptions);
+ }
+
+ /**
+ * Loads an image from a file or URL and creates an internal thumbnail out of it
+ *
+ * @param string filename (with absolute path) of the image to load. If the filename is a http(s) URL, then an attempt to download the file will be made.
+ *
+ * @return boolean True if the image was properly loaded
+ * @throws Exception If the image cannot be loaded, or if its mime type is not supported
+ */
+ public function loadFile($image)
+ {
+ if (eregi('http(s)?://', $image))
+ {
+ if (class_exists('sfWebBrowser'))
+ {
+ if (!is_null($this->tempFile)) {
+ unlink($this->tempFile);
+ }
+ $this->tempFile = tempnam('/tmp', 'sfThumbnailPlugin');
+
+ $b = new sfWebBrowser();
+ try
+ {
+ $b->get($image);
+ if ($b->getResponseCode() != 200) {
+ throw new Exception(sprintf('%s returned error code %s', $image, $b->getResponseCode()));
+ }
+ file_put_contents($this->tempFile, $b->getResponseText());
+ if (!filesize($this->tempFile)) {
+ throw new Exception('downloaded file is empty');
+ } else {
+ $image = $this->tempFile;
+ }
+ }
+ catch (Exception $e)
+ {
+ throw new Exception("Source image is a URL but it cannot be used because ". $e->getMessage());
+ }
+ }
+ else
+ {
+ throw new Exception("Source image is a URL but sfWebBrowserPlugin is not installed");
+ }
+ }
+ else
+ {
+ if (!is_readable($image))
+ {
+ throw new Exception(sprintf('The file "%s" is not readable.', $image));
+ }
+ }
+
+
+ $this->adapter->loadFile($this, $image);
+ }
+
+ /**
+ * Loads an image from a string (e.g. database) and creates an internal thumbnail out of it
+ *
+ * @param string the image string (must be a format accepted by imagecreatefromstring())
+ * @param string mime type of the image
+ *
+ * @return boolean True if the image was properly loaded
+ * @access public
+ * @throws Exception If image mime type is not supported
+ */
+ public function loadData($image, $mime)
+ {
+ $this->adapter->loadData($this, $image, $mime);
+ }
+
+ /**
+ * Saves the thumbnail to the filesystem
+ * If no target mime type is specified, the thumbnail is created with the same mime type as the source file.
+ *
+ * @param string the image thumbnail file destination (with absolute path)
+ * @param string The mime-type of the thumbnail (possible values are 'image/jpeg', 'image/png', and 'image/gif')
+ *
+ * @access public
+ * @return void
+ */
+ public function save($thumbDest, $targetMime = null)
+ {
+ $this->adapter->save($this, $thumbDest, $targetMime);
+ }
+
+ /**
+ * Returns the thumbnail as a string
+ * If no target mime type is specified, the thumbnail is created with the same mime type as the source file.
+ *
+ *
+ * @param string The mime-type of the thumbnail (possible values are adapter dependent)
+ *
+ * @access public
+ * @return string
+ */
+ public function toString($targetMime = null)
+ {
+ return $this->adapter->toString($this, $targetMime);
+ }
+
+ public function toResource()
+ {
+ return $this->adapter->toResource($this);
+ }
+
+ public function freeSource()
+ {
+ if (!is_null($this->tempFile)) {
+ unlink($this->tempFile);
+ }
+ $this->adapter->freeSource();
+ }
+
+ public function freeThumb()
+ {
+ $this->adapter->freeThumb();
+ }
+
+ public function freeAll()
+ {
+ $this->adapter->freeSource();
+ $this->adapter->freeThumb();
+ }
+
+ /**
+ * Returns the width of the thumbnail
+ */
+ public function getThumbWidth()
+ {
+ return $this->thumbWidth;
+ }
+
+ /**
+ * Returns the height of the thumbnail
+ */
+ public function getThumbHeight()
+ {
+ return $this->thumbHeight;
+ }
+
+ /**
+ * Returns the mime type of the source image
+ */
+ public function getMime()
+ {
+ return $this->adapter->getSourceMime();
+ }
+
+ /**
+ * Computes the thumbnail width and height
+ * Used by adapter
+ */
+ public function initThumb($sourceWidth, $sourceHeight, $maxWidth, $maxHeight, $scale, $inflate)
+ {
+ if ($maxWidth > 0)
+ {
+ $ratioWidth = $maxWidth / $sourceWidth;
+ }
+ if ($maxHeight > 0)
+ {
+ $ratioHeight = $maxHeight / $sourceHeight;
+ }
+
+ if ($scale)
+ {
+ if ($maxWidth && $maxHeight)
+ {
+ $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight;
+ }
+ if ($maxWidth xor $maxHeight)
+ {
+ $ratio = (isset($ratioWidth)) ? $ratioWidth : $ratioHeight;
+ }
+ if ((!$maxWidth && !$maxHeight) || (!$inflate && $ratio > 1))
+ {
+ $ratio = 1;
+ }
+
+ $this->thumbWidth = floor($ratio * $sourceWidth);
+ $this->thumbHeight = ceil($ratio * $sourceHeight);
+ }
+ else
+ {
+ if (!isset($ratioWidth) || (!$inflate && $ratioWidth > 1))
+ {
+ $ratioWidth = 1;
+ }
+ if (!isset($ratioHeight) || (!$inflate && $ratioHeight > 1))
+ {
+ $ratioHeight = 1;
+ }
+ $this->thumbWidth = floor($ratioWidth * $sourceWidth);
+ $this->thumbHeight = ceil($ratioHeight * $sourceHeight);
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->freeAll();
+ }
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/LICENSE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/LICENSE Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,7 @@
+Copyright (c) 2004-2008 Francois Zaninotto
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/README Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,269 @@
+sfWebBrowser plugin
+===================
+
+The `sfWebBrowserPlugin` proposes an HTTP client capable of making web requests. The interface is similar to that of `sfTestBrowser`.
+
+Possible uses
+-------------
+
+ * Querying a Web service
+ * Monitoring a Website
+ * Mashup of content from several websites
+ * Aggregation of RSS feeds
+ * Proxy to another server
+ * Cross-domain AJAX interactions
+ * API to foreign websites
+ * Fetch images and other types of content
+ * ...
+
+Contents
+--------
+
+This plugin contains four classes: `sfWebBrowser`, `sfCurlAdapter`, `sfFopenAdapter`, and `sfSocketsAdapter`. Unit tests are available in the SVN repository, to be placed in a symfony application's `test/` directory.
+
+Features
+--------
+
+The `sfWebBrowser` class makes web requests based on a URI:
+
+ [php]
+ $b = new sfWebBrowser();
+ $b->get('http://www.example.com/');
+ $res = $b->getResponseText();
+
+The usual methods of the `sfTestBrowser` also work there, with the fluid interface.
+
+ [php]
+ // Inline
+ $b->get('http://www.example.com/')->get('http://www.google.com/')->back()->reload();
+ // More readable
+ $b->get('http://www.example.com/')
+ ->get('http://www.google.com/')
+ ->back()
+ ->reload();
+
+The browser accepts absolute and relative URIs
+
+ [php]
+ $b->get('http://www.example.com/test.html');
+ $b->get('test.html');
+
+The `get()` method accepts parameters either as a query string, or as an associative array.
+
+ [php]
+ $b->get('http://www.example.com/test.php?foo=bar');
+ $b->get('http://www.example.com/test.php', array('foo' => 'bar'));
+
+POST, PUT and DELETE requests are also supported.
+
+ [php]
+ $b->post('http://www.example.com/test.php', array('foo' => 'bar'));
+ $b->put('http://www.example.com/test.php', array('foo' => 'bar'));
+ $b->delete('http://www.example.com/test.php', array('foo' => 'bar'));
+
+You can access the response in various formats, at your convenience:
+
+ [php]
+ $myString = $b->getResponseText();
+ $myString = $b->getResponseBody(); // drop the part
+ $myDomDocument = $b->getResponseDom();
+ $myDomCssSelector = $b->getResponseDomCssSelector();
+ $mySimpleXml = $b->getResponseXml();
+
+You can also interact with the response with the `setFields()` and `click()` methods.
+
+ [php]
+ $b->get('http://www.example.com/login')
+ ->setField('user', 'foobar')
+ ->setField('password', 'barbaz')
+ ->click('submit');
+
+The browser supports HTTP and HTTPS requests, proxies, redirects, and timeouts.
+
+Gzip and deflate content-encoded response bodies are also supported, provided that you have the [http://php.net/zlib zlib extention] enabled.
+
+Adapters
+--------
+
+The browser can use various adapters to perform the requests, and uses the following selection order by default:
+
+ * `sfCurlAdapter`: Uses [Curl](http://php.net/curl) to fetch pages. This adapter is a lot faster than `sfFopenAdapter`, however PHP must be compiled with the `with-curl` option, and the `curl` extension must be enabled in `php.ini` (which is rarely the case by default) for it to work.
+
+ * `sfFopenAdapter`: Uses [`fopen()`](http://php.net/fopen ) to fetch pages. `fopen()` can take an URL as a parameter provided that PHP is compiled with sockets support, and `allow_url_fopen` is defined to `true` in `php.ini`. This is the case in most PHP distributions, so the default adapter should work in almost every platform. On the other hand, the compatibility has a cost: this adapter is slow.
+
+ * `sfSocketsAdapter`: Uses [`fsockopen()`](http://php.net/fsockopen) to fetch pages.
+
+Alternatively, you can specify an adapter explicitly when you create a new browser object, as follows:
+
+ [php]
+ // use default adapter, i.e. sfCurlAdapter
+ $b = new sfWebBrowser(array());
+ // use sfFopenAdapter
+ $b = new sfWebBrowser(array(), 'sfFopenAdapter');
+
+Currenly, `sfCurlAdapter` offers slightly more functionality than the other adapters. Namely, it supports multipart file uploads and cookies, which means you can login to a site as well as upload files via forms.
+
+ [php]
+ // upload files via a form
+ $b = new sfWebBrowser();
+ $b->post($url_of_form, array(
+ 'file_field' => '/path/to/my/local/file.jpg'
+ ));
+ // login to a website
+ $b = new sfWebBrowser(array(), 'sfCurlAdapter', array('cookies' => true));
+ $b->post($url_of_login_form, array(
+ 'user_field' => $username,
+ 'pass_field' => $password
+ ));
+
+Full examples are available in the unit tests.
+
+Error Handling
+--------------
+
+`sfWebBrowser` distinguishes to types of error: adapter errors and response errors. Thus, `sfWebBrowser` calls should be run this way :
+
+ [php]
+ $b = new sfWebBrowser();
+ try
+ {
+ if (!$b->get($url)->responseIsError())
+ {
+ // Successful response (eg. 200, 201, etc)
+ }
+ else
+ {
+ // Error response (eg. 404, 500, etc)
+ }
+ }
+ catch (Exception $e)
+ {
+ // Adapter error (eg. Host not found)
+ }
+
+Besides, you should always remember that the response contents may contain incorrect code. Consider it as 'tainted', and therefore always use the [escaping](http://www.symfony-project.com/book/trunk/07-Inside-the-View-Layer#Output%20Escaping) when outputting it to a template.
+
+ [php]
+ // In the action
+ $this->title = (string) $b->getResponseXml()->body->h1
+
+ // In the template
+
+ get('title') // correct ?>
+
+Installation
+------------
+
+* Install the plugin
+
+ $ symfony plugin-install http://plugins.symfony-project.com/sfWebBrowserPlugin
+
+
+* Clear the cache to enable the autoloading to find the new class
+
+ $ symfony cc
+
+Known limitations
+-----------------
+
+Cookies, caching, and file uploads are not yet supported in any of the packages (some of this functionality is available with `sfCurlAdapter`, see above).
+
+Changelog
+---------
+
+### Trunk
+
+### 2009-05-12 | 1.1.2 Stable
+
+ * francois: Fixed sfCurlAdapter destructor
+ * francois: Fixed sf1.2 compatibility issue for custom exception
+ * francois: Fixed a few limit case bugs and made the tests pass
+
+### 2009-04-22 | 1.1.1 Stable
+
+ * francois: Fixed README syntax for parameters array
+ * bmeynell: Fixed custom options in `sfCurlAdapter`
+
+### 2008-09-23 | 1.1.0 Stable
+
+ * francois: Translated README to Markdown
+ * francois: Added support for custom options in `sfCurlAdapter`
+ * francois: Added suppot for Timeout with `sfCurlAdapter` (based on a patch by adoanhuu)
+ * blacksun: Allow for SSL certificate verification
+ * francois: Added a test to check exceptions thrown by `getResponseXML`
+ * bmeynell: added multipart file upload support to `sfCurlAdapter`
+ * bmeynell: fixed regex in getResponseBody() which was returning an empty body
+ * bmeynell: `sfCurlAdapter`: Added new options: 'cookies', 'cookies_dir', 'cookies_file', 'verbose', 'verbose_log'
+ * bmeynell: `sfCurlAdapter`: Increased speed by Moving some initialization from call() to the constructer
+ * tristan: Easier management of invalid XML responses
+ * francois: Fixed a bug in `sfFopenAdapter` error handler
+ * bmeynell: Added chunked transfer encoding support to `sfSocketsAdapter`
+ * bmeynell: Added support for 301 redirects in `sfSocketsAdapter`
+
+### 2007-03-27 | 1.0.1 stable
+
+ * bmeynell: Fixed a bug with `sfCurlAdapter` causing 'Bad Request' error responses
+ * francois: Fixed a bug with `get()` when `arg_separator.output` is not set to '&' in `php.ini` (patch from river.bright)
+ * francois: Fixed a bug with `get()` when query string is already present in the url (based on a patch from Jeff Merlet)
+ * francois: Fixed auto-adapter decision in `sfWebBrowser::__construct()`
+
+### 2007-03-08 | 1.0.0 stable
+
+ * francois: Added auto-adapter decision in `sfWebBrowser::__construct()`
+ * francois: Changed tested URLs a bit to avoid redirection issues with google
+ * bmeynell: Added `sfSocketsAdapter`
+ * bmeynell: `sfCurlAdapter`: more detailed error messages & leaner request setup
+
+### 2007-02-22 | 0.9.6 Beta
+
+ * bmeynell, tristan: Allowed for requests with any method in `sfCurlAdapter`
+ * tristan: Added `sfWebBrowser::responseIsError()`
+ * tristan: Added `sfWebBrowser::getResponseMessage()`
+ * tristan: Refactored error management in `sfFopenAdapter`
+
+### 2007-02-21 | 0.9.5 Beta
+
+ * bmeynell: Fixed bug with relative uri's attempting to use a port other than 80 (sfWebBrowser, 132 - 146)
+ * bmeynell: Fixed small bug not printing hostname on exception (sfFopenAdapter, 61-62)
+ * bmeynell: Created sfCurlAdapter and passes all unit tests
+ * bmeynell: Removed '$changeStack = true' from call() prototype in sfCurlAdapter, sfFopenAdapter, and moved changestack check to sfWebBrowser
+ * bmeynell: Added $askeet_url to sfWebBrowserTest
+ * bmeynell: Added easy toggling between adapters in sfWebBrowserTest
+ * tristan: Added put() and delete() public methods
+ * tristan: Added unit tests to validate request HTTP method
+
+### 2007-02-16 | 0.9.4 Beta
+
+ * francois: Refactored the browser to make it multi-adapter
+ * francois: '''BC break''' constructor signature changed : `new sfWebBrowser(array $headers, string $adapter_class, array $adapter_options)`
+ * francois: Fixed notice when trying to retrieve inexistent header
+ * francois: Fixed header case normalization
+ * francois: Transformed setResponseXXX() methods to public
+ * francois: Fixed caps in `initializeRequestHeaders()`
+ * francois: Fixed unit test #40
+
+### 2007-02-16 | 0.9.3 Beta
+
+ * tristan: Added support for gzip and deflate.
+ * tristan: Possibility to pass default request headers to sfWebBrowser's constructor
+ * tristan: "Accept-Encoding" header is automatically set depending on PHP capabilities
+ * tristan: Fixed problems with request and response headers case
+ * tristan: Renamed "browser options" to "adapter options" (http://www.symfony-project.com/forum/index.php/m/21635/)
+ * tristan: '''BC break''' constructor signature changed : `new sfWebBrowser(array $headers, array $adapter_options)`
+ * tristan: Unit tested POST requests
+ * tristan: Changed way httpd headers are stored internally
+ * tristan: Fixed small bug in `getResponseBody()`
+ * francois: Fixed unit test for malformed headers
+
+### 2007-02-09 | 0.9.2 Beta
+
+ * francois: Fixed notice with `getResponseXML()`
+
+### 2007-02-08 | 0.9.1 Beta
+
+ * francois: Fixed notice with some headers
+ * francois: Added license and copyright
+
+### 2007-02-08 | 0.9.0 Beta
+
+ * francois: Initial release
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/lib/sfCurlAdapter.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/lib/sfCurlAdapter.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,237 @@
+
+ * (c) 2004-2006 Fabien Potencier for the click-related functions
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfWebBrowser provides a basic HTTP client.
+ *
+ * @package sfWebBrowserPlugin
+ * @author Francois Zaninotto
+ * @author Ben Meynell
+ * @version 0.9
+ */
+
+class sfCurlAdapter
+{
+ protected
+ $options = array(),
+ $curl = null,
+ $headers = array();
+
+ /**
+ * Build a curl adapter instance
+ * Accepts an option of parameters passed to the PHP curl adapter:
+ * ssl_verify => [true/false]
+ * verbose => [true/false]
+ * verbose_log => [true/false]
+ * Additional options are passed as curl options, under the form:
+ * userpwd => CURL_USERPWD
+ * timeout => CURL_TIMEOUT
+ * ...
+ *
+ * @param array $options Curl-specific options
+ */
+ public function __construct($options = array())
+ {
+ if (!extension_loaded('curl'))
+ {
+ throw new Exception('Curl extension not loaded');
+ }
+
+ $this->options = $options;
+ $curl_options = $options;
+
+ $this->curl = curl_init();
+
+ // cookies
+ if (isset($curl_options['cookies']))
+ {
+ if (isset($curl_options['cookies_file']))
+ {
+ $cookie_file = $curl_options['cookies_file'];
+ unset($curl_options['cookies_file']);
+ }
+ else
+ {
+ $cookie_file = sfConfig::get('sf_data_dir').'/sfWebBrowserPlugin/sfCurlAdapter/cookies.txt';
+ }
+ if (isset($curl_options['cookies_dir']))
+ {
+ $cookie_dir = $curl_options['cookies_dir'];
+ unset($curl_options['cookies_dir']);
+ }
+ else
+ {
+ $cookie_dir = sfConfig::get('sf_data_dir').'/sfWebBrowserPlugin/sfCurlAdapter';
+ }
+ if (!is_dir($cookie_dir))
+ {
+ if (!mkdir($cookie_dir, 0777, true))
+ {
+ throw new Exception(sprintf('Could not create directory "%s"', $cookie_dir));
+ }
+ }
+
+ curl_setopt($this->curl, CURLOPT_COOKIESESSION, false);
+ curl_setopt($this->curl, CURLOPT_COOKIEJAR, $cookie_file);
+ curl_setopt($this->curl, CURLOPT_COOKIEFILE, $cookie_file);
+ unset($curl_options['cookies']);
+ }
+
+ // default settings
+ curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($this->curl, CURLOPT_AUTOREFERER, true);
+ curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, false);
+ curl_setopt($this->curl, CURLOPT_FRESH_CONNECT, true);
+
+ if(isset($this->options['followlocation']))
+ {
+ curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, (bool) $this->options['followlocation']);
+ }
+
+ // activate ssl certificate verification?
+
+ if (isset($this->options['ssl_verify_host']))
+ {
+ curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, (bool) $this->options['ssl_verify_host']);
+ }
+ if (isset($curl_options['ssl_verify']))
+ {
+ curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, (bool) $this->options['ssl_verify']);
+ unset($curl_options['ssl_verify']);
+ }
+ // verbose execution?
+ if (isset($curl_options['verbose']))
+ {
+ curl_setopt($this->curl, CURLOPT_NOPROGRESS, false);
+ curl_setopt($this->curl, CURLOPT_VERBOSE, true);
+ unset($curl_options['cookies']);
+ }
+ if (isset($curl_options['verbose_log']))
+ {
+ $log_file = sfConfig::get('sf_log_dir').'/sfCurlAdapter_verbose.log';
+ curl_setopt($this->curl, CURLOPT_VERBOSE, true);
+ $this->fh = fopen($log_file, 'a+b');
+ curl_setopt($this->curl, CURLOPT_STDERR, $this->fh);
+ unset($curl_options['verbose_log']);
+ }
+
+ // Additional options
+ foreach ($curl_options as $key => $value)
+ {
+ $const = constant('CURLOPT_' . strtoupper($key));
+ if(!is_null($const))
+ {
+ curl_setopt($this->curl, $const, $value);
+ }
+ }
+
+ // response header storage - uses callback function
+ curl_setopt($this->curl, CURLOPT_HEADERFUNCTION, array($this, 'read_header'));
+ }
+
+ /**
+ * Submits a request
+ *
+ * @param string The request uri
+ * @param string The request method
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function call($browser, $uri, $method = 'GET', $parameters = array(), $headers = array())
+ {
+ // uri
+ curl_setopt($this->curl, CURLOPT_URL, $uri);
+
+ // request headers
+ $m_headers = array_merge($browser->getDefaultRequestHeaders(), $browser->initializeRequestHeaders($headers));
+ $request_headers = explode("\r\n", $browser->prepareHeaders($m_headers));
+ curl_setopt($this->curl, CURLOPT_HTTPHEADER, $request_headers);
+
+ // encoding support
+ if(isset($headers['Accept-Encoding']))
+ {
+ curl_setopt($this->curl, CURLOPT_ENCODING, $headers['Accept-Encoding']);
+ }
+
+ // timeout support
+ if(isset($this->options['Timeout']))
+ {
+ curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->options['Timeout']);
+ }
+
+ if (!empty($parameters))
+ {
+ if (!is_array($parameters))
+ {
+ curl_setopt($this->curl, CURLOPT_POSTFIELDS, $parameters);
+ }
+ else
+ {
+ // multipart posts (file upload support)
+ $has_files = false;
+ foreach ($parameters as $name => $value)
+ {
+ if (is_array($value)) {
+ continue;
+ }
+ if (is_file($value))
+ {
+ $has_files = true;
+ $parameters[$name] = '@'.realpath($value);
+ }
+ }
+ if($has_files)
+ {
+ curl_setopt($this->curl, CURLOPT_POSTFIELDS, $parameters);
+ }
+ else
+ {
+ curl_setopt($this->curl, CURLOPT_POSTFIELDS, http_build_query($parameters, '', '&'));
+ }
+ }
+ }
+
+ // handle any request method
+ curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
+
+ $response = curl_exec($this->curl);
+
+ if (curl_errno($this->curl))
+ {
+ throw new Exception(curl_error($this->curl));
+ }
+
+ $requestInfo = curl_getinfo($this->curl);
+
+ $browser->setResponseCode($requestInfo['http_code']);
+ $browser->setResponseHeaders($this->headers);
+ $browser->setResponseText($response);
+
+ // clear response headers
+ $this->headers = array();
+
+ return $browser;
+ }
+
+ public function __destruct()
+ {
+ curl_close($this->curl);
+ }
+
+ protected function read_header($curl, $headers)
+ {
+ $this->headers[] = $headers;
+
+ return strlen($headers);
+ }
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/lib/sfFopenAdapter.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/lib/sfFopenAdapter.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,123 @@
+
+ * (c) 2004-2006 Fabien Potencier for the click-related functions
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfWebBrowser provides a basic HTTP client.
+ *
+ * @package sfWebBrowserPlugin
+ * @author Francois Zaninotto
+ * @version 0.9
+ */
+class sfFopenAdapter
+{
+ protected
+ $options = array(),
+ $adapterErrorMessage = null,
+ $browser = null;
+
+ public function __construct($options = array())
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * Submits a request
+ *
+ * @param string The request uri
+ * @param string The request method
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ * @param boolean To specify is the request changes the browser history
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function call($browser, $uri, $method = 'GET', $parameters = array(), $headers = array())
+ {
+ $m_headers = array_merge(array('Content-Type' => 'application/x-www-form-urlencoded'), $browser->getDefaultRequestHeaders(), $browser->initializeRequestHeaders($headers));
+ $request_headers = $browser->prepareHeaders($m_headers);
+
+ // Read the response from the server
+ // FIXME: use sockets to avoid depending on allow_url_fopen
+ $context = stream_context_create(array('http' => array_merge(
+ $this->options,
+ array('method' => $method),
+ array('content' => is_array($parameters) ? http_build_query($parameters) : $parameters),
+ array('header' => $request_headers)
+ )));
+
+ // Custom error handler
+ // -- browser instance must be accessible from handleRuntimeError()
+ $this->browser = $browser;
+ set_error_handler(array($this, 'handleRuntimeError'), E_WARNING);
+ if($handle = fopen($uri, 'r', false, $context))
+ {
+ $response_headers = stream_get_meta_data($handle);
+ $browser->setResponseCode(array_shift($response_headers['wrapper_data']));
+ $browser->setResponseHeaders($response_headers['wrapper_data']);
+ $browser->setResponseText(stream_get_contents($handle));
+ fclose($handle);
+ }
+
+ restore_error_handler();
+
+ if ($this->adapterErrorMessage == true)
+ {
+ $msg = $this->adapterErrorMessage;
+ $this->adapterErrorMessage = null;
+ throw new Exception($msg);
+ }
+
+ return $browser;
+ }
+
+ /**
+ * Handles PHP runtime error.
+ *
+ * This handler is used to catch any warnigns sent by fopen($url) and reformat them to something
+ * usable.
+ *
+ * @see http://php.net/set_error_handler
+ */
+ function handleRuntimeError($errno, $errstr, $errfile = null, $errline = null, $errcontext = array() )
+ {
+ $error_types = array (
+ E_ERROR => 'Error',
+ E_WARNING => 'Warning',
+ E_PARSE => 'Parsing Error',
+ E_NOTICE => 'Notice',
+ E_CORE_ERROR => 'Core Error',
+ E_CORE_WARNING => 'Core Warning',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_COMPILE_WARNING => 'Compile Warning',
+ E_USER_ERROR => 'User Error',
+ E_USER_WARNING => 'User Warning',
+ E_USER_NOTICE => 'User Notice',
+ E_STRICT => 'Runtime Notice',
+ E_RECOVERABLE_ERROR => 'Catchable Fatal Error'
+ );
+
+ $msg = sprintf('%s : "%s" occured in %s on line %d',
+ $error_types[$errno], $errstr, $errfile, $errline);
+
+ $matches = array();
+ if (preg_match('/HTTP\/\d\.\d (\d{3}) (.*)$/', $errstr, $matches))
+ {
+ $this->browser->setResponseCode($matches[1]);
+ $this->browser->setResponseMessage($matches[2]);
+ $body = sprintf('The %s adapter cannot handle error responses body. Try using another adapter.', __CLASS__);
+ $this->browser->setResponseText($body);
+ }
+ else
+ {
+ $this->adapterErrorMessage = $msg;
+ }
+ }
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/lib/sfSocketsAdapter.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/lib/sfSocketsAdapter.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,152 @@
+
+ * (c) 2004-2006 Fabien Potencier for the click-related functions
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfWebBrowser provides a basic HTTP client.
+ *
+ * @package sfWebBrowserPlugin
+ * @author Francois Zaninotto
+ * @author Benjamin Meynell
+ * @version 0.9
+ */
+class sfSocketsAdapter
+{
+ protected
+ $options = array(),
+ $adapterErrorMessage = null,
+ $browser = null;
+
+ public function __construct($options = array())
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * Submits a request
+ *
+ * @param string The request uri
+ * @param string The request method
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function call($browser, $uri, $method = 'GET', $parameters = array(), $headers = array())
+ {
+ $m_headers = array_merge(array('Content-Type' => 'application/x-www-form-urlencoded'), $browser->getDefaultRequestHeaders(), $browser->initializeRequestHeaders($headers));
+ $request_headers = $browser->prepareHeaders($m_headers);
+
+ $url_info = parse_url($uri);
+
+ // initialize default values
+ isset($url_info['path']) ? $path = $url_info['path'] : $path = '/';
+ isset($url_info['query']) ? $qstring = '?'.$url_info['query'] : $qstring = null;
+ isset($url_info['port']) ? null : $url_info['port'] = 80;
+
+ if (!$socket = @fsockopen($url_info['host'], $url_info['port'], $errno, $errstr, 15))
+ {
+ throw new Exception("Could not connect ($errno): $errstr");
+ }
+
+ // build request
+ $request = "$method $path$qstring HTTP/1.1\r\n";
+ $request .= 'Host: '.$url_info['host'].':'.$url_info['port']."\r\n";
+ $request .= $request_headers;
+ $request .= "Connection: Close\r\n";
+
+ if ($method == 'PUT' && is_array($parameters) && array_key_exists('file', $parameters))
+ {
+ $fp = fopen($parameters['file'], 'rb');
+ $sent = 0;
+ $blocksize = (2 << 20); // 2MB chunks
+ $filesize = filesize($parameters['file']);
+
+ $request .= 'Content-Length: '.$filesize."\r\n";
+
+ $request .= "\r\n";
+
+ fwrite($socket, $request);
+
+ while ($sent < $filesize)
+ {
+ $data = fread($fp, $blocksize);
+ fwrite($socket, $data);
+ $sent += $blocksize;
+ }
+ fclose($fp);
+ }
+ elseif ($method == 'POST' || $method == 'PUT')
+ {
+ $body = is_array($parameters) ? http_build_query($parameters, '', '&') : $parameters;
+ $request .= 'Content-Length: '.strlen($body)."\r\n";
+ $request .= "\r\n";
+ $request .= $body;
+ }
+
+ $request .= "\r\n";
+
+ fwrite($socket, $request);
+
+ $response = '';
+ $response_body = '';
+ while (!feof($socket))
+ {
+ $response .= fgets($socket, 1024);
+ }
+ fclose($socket);
+
+ // parse response components: status line, headers and body
+ $response_lines = explode("\r\n", $response);
+
+ // http status line (ie "HTTP 1.1 200 OK")
+ $status_line = array_shift($response_lines);
+
+ $start_body = false;
+ $response_headers = array();
+ for($i=0; $isetResponseHeaders($response_headers);
+
+ // grab status code
+ preg_match('@(\d{3})@', $status_line, $status_code);
+ if(isset($status_code[1]))
+ {
+ $browser->setResponseCode($status_code[1]);
+ }
+ $browser->setResponseText(trim($response_body));
+
+ return $browser;
+ }
+
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/lib/sfWebBrowser.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/lib/sfWebBrowser.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,851 @@
+
+ * (c) 2004-2006 Fabien Potencier for the click-related functions
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfWebBrowser provides a basic HTTP client.
+ *
+ * @package sfWebBrowserPlugin
+ * @author Francois Zaninotto
+ * @author Tristan Rivoallan
+ * @version 0.9
+ */
+class sfWebBrowser
+{
+ protected
+ $defaultHeaders = array(),
+ $stack = array(),
+ $stackPosition = -1,
+ $responseHeaders = array(),
+ $responseCode = '',
+ $responseMessage = '',
+ $responseText = '',
+ $responseDom = null,
+ $responseDomCssSelector = null,
+ $responseXml = null,
+ $fields = array(),
+ $urlInfo = array();
+
+ public function __construct($defaultHeaders = array(), $adapterClass = null, $adapterOptions = array())
+ {
+ if(!$adapterClass)
+ {
+ if (function_exists('curl_init'))
+ {
+ $adapterClass = 'sfCurlAdapter';
+ }
+ else if(ini_get('allow_url_fopen') == 1)
+ {
+ $adapterClass = 'sfFopenAdapter';
+ }
+ else
+ {
+ $adapterClass = 'sfSocketsAdapter';
+ }
+ }
+ $this->defaultHeaders = $this->fixHeaders($defaultHeaders);
+ $this->adapter = new $adapterClass($adapterOptions);
+ }
+
+ // Browser methods
+
+ /**
+ * Restarts the browser
+ *
+ * @param array default browser options
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function restart($defaultHeaders = array())
+ {
+ $this->defaultHeaders = $this->fixHeaders($defaultHeaders);
+ $this->stack = array();
+ $this->stackPosition = -1;
+ $this->urlInfo = array();
+ $this->initializeResponse();
+
+ return $this;
+ }
+
+ /**
+ * Sets the browser user agent name
+ *
+ * @param string agent name
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function setUserAgent($agent)
+ {
+ $this->defaultHeaders['User-Agent'] = $agent;
+
+ return $this;
+ }
+
+ /**
+ * Gets the browser user agent name
+ *
+ * @return string agent name
+ */
+ public function getUserAgent()
+ {
+ return isset($this->defaultHeaders['User-Agent']) ? $this->defaultHeaders['User-Agent'] : '';
+ }
+
+ /**
+ * Submits a GET request
+ *
+ * @param string The request uri
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function get($uri, $parameters = array(), $headers = array())
+ {
+ if ($parameters)
+ {
+ $uri .= ((false !== strpos($uri, '?')) ? '&' : '?') . http_build_query($parameters, '', '&');
+ }
+ return $this->call($uri, 'GET', array(), $headers);
+ }
+
+ public function head($uri, $parameters = array(), $headers = array())
+ {
+ if ($parameters)
+ {
+ $uri .= ((false !== strpos($uri, '?')) ? '&' : '?') . http_build_query($parameters, '', '&');
+ }
+ return $this->call($uri, 'HEAD', array(), $headers);
+ }
+
+ /**
+ * Submits a POST request
+ *
+ * @param string The request uri
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function post($uri, $parameters = array(), $headers = array())
+ {
+ return $this->call($uri, 'POST', $parameters, $headers);
+ }
+
+ /**
+ * Submits a PUT request.
+ *
+ * @param string The request uri
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function put($uri, $parameters = array(), $headers = array())
+ {
+ return $this->call($uri, 'PUT', $parameters, $headers);
+ }
+
+ /**
+ * Submits a DELETE request.
+ *
+ * @param string The request uri
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function delete($uri, $parameters = array(), $headers = array())
+ {
+ return $this->call($uri, 'DELETE', $parameters, $headers);
+ }
+
+ /**
+ * Submits a request
+ *
+ * @param string The request uri
+ * @param string The request method
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ * @param boolean To specify is the request changes the browser history
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function call($uri, $method = 'GET', $parameters = array(), $headers = array(), $changeStack = true)
+ {
+ $urlInfo = parse_url($uri);
+
+ // Check headers
+ $headers = $this->fixHeaders($headers);
+
+ // check port
+ if (isset($urlInfo['port']))
+ {
+ $this->urlInfo['port'] = $urlInfo['port'];
+ }
+ else if (!isset($this->urlInfo['port']))
+ {
+ $this->urlInfo['port'] = 80;
+ }
+
+ if(!isset($urlInfo['host']))
+ {
+ // relative link
+ $uri = $this->urlInfo['scheme'].'://'.$this->urlInfo['host'].':'.$this->urlInfo['port'].'/'.$uri;
+ }
+ else if($urlInfo['scheme'] != 'http' && $urlInfo['scheme'] != 'https')
+ {
+ throw new Exception('sfWebBrowser handles only http and https requests');
+ }
+
+ $this->urlInfo = parse_url($uri);
+
+ $this->initializeResponse();
+
+ if ($changeStack)
+ {
+ $this->addToStack($uri, $method, $parameters, $headers);
+ }
+
+ $browser = $this->adapter->call($this, $uri, $method, $parameters, $headers);
+
+ // redirect support
+ if ((in_array($browser->getResponseCode(), array(301, 307)) && in_array($method, array('GET', 'HEAD'))) || in_array($browser->getResponseCode(), array(302,303)))
+ {
+ $this->call($browser->getResponseHeader('Location'), 'GET', array(), $headers);
+ }
+
+ return $browser;
+ }
+
+ /**
+ * Gives a value to a form field in the response
+ *
+ * @param string field name
+ * @param string field value
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function setField($name, $value)
+ {
+ // as we don't know yet the form, just store name/value pairs
+ $this->parseArgumentAsArray($name, $value, $this->fields);
+
+ return $this;
+ }
+
+ /**
+ * Looks for a link or a button in the response and submits the related request
+ *
+ * @param string The link/button value/href/alt
+ * @param array request parameters (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function click($name, $arguments = array())
+ {
+ if (!$dom = $this->getResponseDom())
+ {
+ throw new Exception('Cannot click because there is no current page in the browser');
+ }
+
+ $xpath = new DomXpath($dom);
+
+ // text link, the name being in an attribute
+ if ($link = $xpath->query(sprintf('//a[@*="%s"]', $name))->item(0))
+ {
+ return $this->get($link->getAttribute('href'));
+ }
+
+ // text link, the name being the text value
+ if ($links = $xpath->query('//a[@href]'))
+ {
+ foreach($links as $link)
+ {
+ if(preg_replace(array('/\s{2,}/', '/\\r\\n|\\n|\\r/'), array(' ', ''), $link->nodeValue) == $name)
+ {
+ return $this->get($link->getAttribute('href'));
+ }
+ }
+ }
+
+ // image link, the name being the alt attribute value
+ if ($link = $xpath->query(sprintf('//a/img[@alt="%s"]/ancestor::a', $name))->item(0))
+ {
+ return $this->get($link->getAttribute('href'));
+ }
+
+ // form, the name being the button or input value
+ if (!$form = $xpath->query(sprintf('//input[((@type="submit" or @type="button") and @value="%s") or (@type="image" and @alt="%s")]/ancestor::form', $name, $name))->item(0))
+ {
+ throw new Exception(sprintf('Cannot find the "%s" link or button.', $name));
+ }
+
+ // form attributes
+ $url = $form->getAttribute('action');
+ $method = $form->getAttribute('method') ? strtolower($form->getAttribute('method')) : 'get';
+
+ // merge form default values and arguments
+ $defaults = array();
+ foreach ($xpath->query('descendant::input | descendant::textarea | descendant::select', $form) as $element)
+ {
+ $elementName = $element->getAttribute('name');
+ $nodeName = $element->nodeName;
+ $value = null;
+ if ($nodeName == 'input' && ($element->getAttribute('type') == 'checkbox' || $element->getAttribute('type') == 'radio'))
+ {
+ if ($element->getAttribute('checked'))
+ {
+ $value = $element->getAttribute('value');
+ }
+ }
+ else if (
+ $nodeName == 'input'
+ &&
+ (($element->getAttribute('type') != 'submit' && $element->getAttribute('type') != 'button') || $element->getAttribute('value') == $name)
+ &&
+ ($element->getAttribute('type') != 'image' || $element->getAttribute('alt') == $name)
+ )
+ {
+ $value = $element->getAttribute('value');
+ }
+ else if ($nodeName == 'textarea')
+ {
+ $value = '';
+ foreach ($element->childNodes as $el)
+ {
+ $value .= $dom->saveXML($el);
+ }
+ }
+ else if ($nodeName == 'select')
+ {
+ if ($multiple = $element->hasAttribute('multiple'))
+ {
+ $elementName = str_replace('[]', '', $elementName);
+ $value = array();
+ }
+ else
+ {
+ $value = null;
+ }
+
+ $found = false;
+ foreach ($xpath->query('descendant::option', $element) as $option)
+ {
+ if ($option->getAttribute('selected'))
+ {
+ $found = true;
+ if ($multiple)
+ {
+ $value[] = $option->getAttribute('value');
+ }
+ else
+ {
+ $value = $option->getAttribute('value');
+ }
+ }
+ }
+
+ // if no option is selected and if it is a simple select box, take the first option as the value
+ if (!$found && !$multiple)
+ {
+ $value = $xpath->query('descendant::option', $element)->item(0)->getAttribute('value');
+ }
+ }
+
+ if (null !== $value)
+ {
+ $this->parseArgumentAsArray($elementName, $value, $defaults);
+ }
+ }
+
+ // create request parameters
+ $arguments = sfToolkit::arrayDeepMerge($defaults, $this->fields, $arguments);
+ if ('post' == $method)
+ {
+ return $this->post($url, $arguments);
+ }
+ else
+ {
+ return $this->get($url, $arguments);
+ }
+ }
+
+ protected function parseArgumentAsArray($name, $value, &$vars)
+ {
+ if (false !== $pos = strpos($name, '['))
+ {
+ $var = &$vars;
+ $tmps = array_filter(preg_split('/(\[ | \[\] | \])/x', $name));
+ foreach ($tmps as $tmp)
+ {
+ $var = &$var[$tmp];
+ }
+ if ($var)
+ {
+ if (!is_array($var))
+ {
+ $var = array($var);
+ }
+ $var[] = $value;
+ }
+ else
+ {
+ $var = $value;
+ }
+ }
+ else
+ {
+ $vars[$name] = $value;
+ }
+ }
+
+ /**
+ * Adds the current request to the history stack
+ *
+ * @param string The request uri
+ * @param string The request method
+ * @param array The request parameters (associative array)
+ * @param array The request headers (associative array)
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function addToStack($uri, $method, $parameters, $headers)
+ {
+ $this->stack = array_slice($this->stack, 0, $this->stackPosition + 1);
+ $this->stack[] = array(
+ 'uri' => $uri,
+ 'method' => $method,
+ 'parameters' => $parameters,
+ 'headers' => $headers
+ );
+ $this->stackPosition = count($this->stack) - 1;
+
+ return $this;
+ }
+
+ /**
+ * Submits the previous request in history again
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function back()
+ {
+ if ($this->stackPosition < 1)
+ {
+ throw new Exception('You are already on the first page.');
+ }
+
+ --$this->stackPosition;
+ return $this->call($this->stack[$this->stackPosition]['uri'],
+ $this->stack[$this->stackPosition]['method'],
+ $this->stack[$this->stackPosition]['parameters'],
+ $this->stack[$this->stackPosition]['headers'],
+ false);
+ }
+
+ /**
+ * Submits the next request in history again
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function forward()
+ {
+ if ($this->stackPosition > count($this->stack) - 2)
+ {
+ throw new Exception('You are already on the last page.');
+ }
+
+ ++$this->stackPosition;
+ return $this->call($this->stack[$this->stackPosition]['uri'],
+ $this->stack[$this->stackPosition]['method'],
+ $this->stack[$this->stackPosition]['parameters'],
+ $this->stack[$this->stackPosition]['headers'],
+ false);
+ }
+
+ /**
+ * Submits the current request again
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function reload()
+ {
+ if (-1 == $this->stackPosition)
+ {
+ throw new Exception('No page to reload.');
+ }
+
+ return $this->call($this->stack[$this->stackPosition]['uri'],
+ $this->stack[$this->stackPosition]['method'],
+ $this->stack[$this->stackPosition]['parameters'],
+ $this->stack[$this->stackPosition]['headers'],
+ false);
+ }
+
+ /**
+ * Transforms an associative array of header names => header values to its HTTP equivalent.
+ *
+ * @param array $headers
+ * @return string
+ */
+ public function prepareHeaders($headers = array())
+ {
+ $prepared_headers = array();
+ foreach ($headers as $name => $value)
+ {
+ $prepared_headers[] = sprintf("%s: %s\r\n", ucfirst($name), $value);
+ }
+
+ return implode('', $prepared_headers);
+ }
+
+ // Response methods
+
+ /**
+ * Initializes the response and erases all content from prior requests
+ */
+ public function initializeResponse()
+ {
+ $this->responseHeaders = array();
+ $this->responseCode = '';
+ $this->responseText = '';
+ $this->responseDom = null;
+ $this->responseDomCssSelector = null;
+ $this->responseXml = null;
+ $this->fields = array();
+ }
+
+ /**
+ * Set the response headers
+ *
+ * @param array The response headers as an array of strings shaped like "key: value"
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function setResponseHeaders($headers = array())
+ {
+ $header_array = array();
+ foreach($headers as $header)
+ {
+ $arr = explode(': ', $header);
+ if(isset($arr[1]))
+ {
+ $header_array[$this->normalizeHeaderName($arr[0])] = trim($arr[1]);
+ }
+ }
+
+ $this->responseHeaders = $header_array;
+
+ return $this;
+ }
+
+ /**
+ * Set the response code
+ *
+ * @param string The first line of the response
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function setResponseCode($firstLine)
+ {
+ preg_match('/\d{3}/', $firstLine, $matches);
+ if(isset($matches[0]))
+ {
+ $this->responseCode = $matches[0];
+ }
+ else
+ {
+ $this->responseCode = '';
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the response contents
+ *
+ * @param string The response contents
+ *
+ * @return sfWebBrowser The current browser object
+ */
+ public function setResponseText($res)
+ {
+ $this->responseText = $res;
+
+ return $this;
+ }
+
+ /**
+ * Get a text version of the response
+ *
+ * @return string The response contents
+ */
+ public function getResponseText()
+ {
+ $text = $this->responseText;
+
+ // Decode any content-encoding (gzip or deflate) if needed
+ switch (strtolower($this->getResponseHeader('content-encoding'))) {
+
+ // Handle gzip encoding
+ case 'gzip':
+ $text = $this->decodeGzip($text);
+ break;
+
+ // Handle deflate encoding
+ case 'deflate':
+ $text = $this->decodeDeflate($text);
+ break;
+
+ default:
+ break;
+ }
+
+ return $text;
+ }
+
+ /**
+ * Get a text version of the body part of the response (without and )
+ *
+ * @return string The body part of the response contents
+ */
+ public function getResponseBody()
+ {
+ preg_match('/(.*)<\/body>/si', $this->getResponseText(), $matches);
+
+ return isset($matches[1]) ? $matches[1] : '';
+ }
+
+ /**
+ * Get a DOMDocument version of the response
+ *
+ * @return DOMDocument The reponse contents
+ */
+ public function getResponseDom()
+ {
+ if(!$this->responseDom)
+ {
+ // for HTML/XML content, create a DOM object for the response content
+ if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
+ {
+ $this->responseDom = new DomDocument('1.0', 'utf8');
+ $this->responseDom->validateOnParse = true;
+ @$this->responseDom->loadHTML($this->getResponseText());
+ }
+ }
+
+ return $this->responseDom;
+ }
+
+ /**
+ * Get a sfDomCssSelector version of the response
+ *
+ * @return sfDomCssSelector The response contents
+ */
+ public function getResponseDomCssSelector()
+ {
+ if(!$this->responseDomCssSelector)
+ {
+ // for HTML/XML content, create a DOM object for the response content
+ if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
+ {
+ $this->responseDomCssSelector = new sfDomCssSelector($this->getResponseDom());
+ }
+ }
+
+ return $this->responseDomCssSelector;
+ }
+
+ /**
+ * Get a SimpleXML version of the response
+ *
+ * @return SimpleXMLElement The reponse contents
+ * @throws sfWebBrowserInvalidResponseException when response is not in a valid format
+ */
+ public function getResponseXML()
+ {
+ if(!$this->responseXml)
+ {
+ // for HTML/XML content, create a DOM object for the response content
+ if (preg_match('/(x|ht)ml/i', $this->getResponseHeader('Content-Type')))
+ {
+ $this->responseXml = @simplexml_load_string($this->getResponseText());
+ }
+ }
+
+ // Throw an exception if response is not valid XML
+ if (get_class($this->responseXml) != 'SimpleXMLElement')
+ {
+ $msg = sprintf("Response is not a valid XML string : \n%s", $this->getResponseText());
+ throw new sfWebBrowserInvalidResponseException($msg);
+ }
+
+ return $this->responseXml;
+ }
+
+ /**
+ * Returns true if server response is an error.
+ *
+ * @return bool
+ */
+ public function responseIsError()
+ {
+ return in_array((int)($this->getResponseCode() / 100), array(4, 5));
+ }
+
+ /**
+ * Get the response headers
+ *
+ * @return array The response headers
+ */
+ public function getResponseHeaders()
+ {
+ return $this->responseHeaders;
+ }
+
+ /**
+ * Get a response header
+ *
+ * @param string The response header name
+ *
+ * @return string The response header value
+ */
+ public function getResponseHeader($key)
+ {
+ $normalized_key = $this->normalizeHeaderName($key);
+ return (isset($this->responseHeaders[$normalized_key])) ? $this->responseHeaders[$normalized_key] : '';
+ }
+
+ /**
+ * Decodes gzip-encoded content ("content-encoding: gzip" response header).
+ *
+ * @param stream $gzip_text
+ * @return string
+ */
+ protected function decodeGzip($gzip_text)
+ {
+ return gzinflate(substr($gzip_text, 10));
+ }
+
+ /**
+ * Decodes deflate-encoded content ("content-encoding: deflate" response header).
+ *
+ * @param stream $deflate_text
+ * @return string
+ */
+ protected function decodeDeflate($deflate_text)
+ {
+ return gzuncompress($deflate_text);
+ }
+
+ /**
+ * Get the response code
+ *
+ * @return string The response code
+ */
+ public function getResponseCode()
+ {
+ return $this->responseCode;
+ }
+
+ /**
+ * Returns the response message (the 'Not Found' part in 'HTTP/1.1 404 Not Found')
+ *
+ * @return string
+ */
+ public function getResponseMessage()
+ {
+ return $this->responseMessage;
+ }
+
+ /**
+ * Sets response message.
+ *
+ * @param string $message
+ */
+ public function setResponseMessage($msg)
+ {
+ $this->responseMessage = $msg;
+ }
+
+ public function getUrlInfo()
+ {
+ return $this->urlInfo;
+ }
+
+ public function getDefaultRequestHeaders()
+ {
+ return $this->defaultHeaders;
+ }
+
+ /**
+ * Adds default headers to the supplied headers array.
+ *
+ * @param array $headers
+ * @return array
+ */
+ public function initializeRequestHeaders($headers = array())
+ {
+ // Supported encodings
+ $encodings = array();
+ if (isset($headers['Accept-Encoding']))
+ {
+ $encodings = explode(',', $headers['Accept-Encoding']);
+ }
+ if (function_exists('gzinflate') && !in_array('gzip', $encodings))
+ {
+ $encodings[] = 'gzip';
+ }
+ if (function_exists('gzuncompress') && !in_array('deflate', $encodings))
+ {
+ $encodings[] = 'deflate';
+ }
+
+ $headers['Accept-Encoding'] = implode(',', array_unique($encodings));
+
+ return $headers;
+ }
+
+ /**
+ * Validates supplied headers and turns all names to lowercase.
+ *
+ * @param array $headers
+ * @return array
+ */
+ private function fixHeaders($headers)
+ {
+ $fixed_headers = array();
+ foreach ($headers as $name => $value)
+ {
+ if (!preg_match('/([a-z]*)(-[a-z]*)*/i', $name))
+ {
+ $msg = sprintf('Invalid header "%s"', $name);
+ throw new Exception($msg);
+ }
+ $fixed_headers[$this->normalizeHeaderName($name)] = trim($value);
+ }
+
+ return $fixed_headers;
+ }
+
+ /**
+ * Retrieves a normalized Header.
+ *
+ * @param string Header name
+ *
+ * @return string Normalized header
+ */
+ protected function normalizeHeaderName($name)
+ {
+ return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", strtr(ucfirst($name), '_', '-'));
+ }
+
+}
diff -r 3189b5c66e3d -r 2fda205cc5de web/thdProject/plugins/sfWebBrowserPlugin/lib/sfWebBrowserInvalidResponseException.class.php
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/thdProject/plugins/sfWebBrowserPlugin/lib/sfWebBrowserInvalidResponseException.class.php Wed Apr 28 17:18:44 2010 +0200
@@ -0,0 +1,25 @@
+
+ */
+class sfWebBrowserInvalidResponseException extends sfException
+{
+ /**
+ * Class constructor.
+ *
+ * @param string The error message
+ * @param int The error code
+ */
+ public function __construct($message = null, $code = 0)
+ {
+ if(method_exists($this, 'setName'))
+ {
+ $this->setName('sfWebBrowserInvalidResponseException');
+ }
+ parent::__construct($message, $code);
+ }
+}