From c56bee5022a9b2c6f7e47a5f5a8de38efc20b572 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Sun, 4 Dec 2011 16:50:13 +0100 Subject: [PATCH] First commit --- .../EDateFormatBehavior.php | 55 + behaviors/EJsonBehavior/EJsonBehavior.php | 38 + extensions/EFeed/EFeed.php | 465 ++++++ extensions/EFeed/EFeedItemAbstract.php | 137 ++ extensions/EFeed/EFeedItemAtom.php | 121 ++ extensions/EFeed/EFeedItemRSS1.php | 108 ++ extensions/EFeed/EFeedItemRSS2.php | 119 ++ extensions/EFeed/EFeedTag.php | 58 + extensions/EGMap/EGMap.php | 1254 +++++++++++++++++ extensions/EGMap/EGMapApiKeyList.php | 98 ++ extensions/EGMap/EGMapBase.php | 134 ++ extensions/EGMap/EGMapBounds.php | 439 ++++++ extensions/EGMap/EGMapClient.php | 256 ++++ extensions/EGMap/EGMapControlPosition.php | 42 + extensions/EGMap/EGMapCoord.php | 429 ++++++ extensions/EGMap/EGMapDirection.php | 177 +++ extensions/EGMap/EGMapDirectionRenderer.php | 123 ++ extensions/EGMap/EGMapDirectionWayPoint.php | 117 ++ extensions/EGMap/EGMapEvent.php | 154 ++ extensions/EGMap/EGMapGeocodeTool.php | 303 ++++ extensions/EGMap/EGMapGeocodedAddress.php | 341 +++++ extensions/EGMap/EGMapInfoBox.php | 144 ++ extensions/EGMap/EGMapInfoWindow.php | 257 ++++ extensions/EGMap/EGMapKMLService.php | 52 + extensions/EGMap/EGMapKeyDragZoom.php | 177 +++ extensions/EGMap/EGMapLatLonControl.php | 43 + extensions/EGMap/EGMapMarker.php | 405 ++++++ extensions/EGMap/EGMapMarkerClusterer.php | 125 ++ extensions/EGMap/EGMapMarkerImage.php | 255 ++++ extensions/EGMap/EGMapMarkerWithLabel.php | 147 ++ extensions/EGMap/EGMapPoint.php | 89 ++ extensions/EGMap/EGMapSize.php | 91 ++ extensions/EGMap/assets/geoxml3.js | 1018 +++++++++++++ extensions/EGMap/assets/infobox_packed.js | 1 + extensions/EGMap/assets/keydragzoom_packed.js | 1 + extensions/EGMap/assets/latloncontrol.js | 81 ++ .../EGMap/assets/markerclusterer_packed.js | 1 + extensions/EGMap/assets/markers/m1.png | Bin 0 -> 3003 bytes extensions/EGMap/assets/markers/m2.png | Bin 0 -> 3259 bytes extensions/EGMap/assets/markers/m3.png | Bin 0 -> 3956 bytes extensions/EGMap/assets/markers/m4.png | Bin 0 -> 5705 bytes extensions/EGMap/assets/markers/m5.png | Bin 0 -> 6839 bytes .../EGMap/assets/markerwithlabel_packed.js | 1 + extensions/EGMap/kml/EGMapKMLFeed.php | 394 ++++++ extensions/EGMap/kml/EGMapKMLIconStyle.php | 74 + extensions/EGMap/kml/EGMapKMLLineString.php | 86 ++ extensions/EGMap/kml/EGMapKMLLineStyle.php | 86 ++ extensions/EGMap/kml/EGMapKMLNode.php | 139 ++ extensions/EGMap/kml/EGMapKMLPlacemark.php | 76 + extensions/EGMap/kml/EGMapKMLPoint.php | 95 ++ extensions/EGMap/kml/EGMapKMLPolyStyle.php | 61 + extensions/EGMap/kml/EGMapKMLPolygon.php | 131 ++ extensions/EGeoIp/EGeoIP.php | 232 +++ .../EGeoNameService/EGeoNameService.php | 550 ++++++++ extensions/EHttpClient/ECookieJar.php | 353 +++++ extensions/EHttpClient/EHostnameValidator.php | 430 ++++++ extensions/EHttpClient/EHttpClient.php | 1148 +++++++++++++++ extensions/EHttpClient/EHttpCookie.php | 335 +++++ extensions/EHttpClient/EHttpResponse.php | 631 +++++++++ extensions/EHttpClient/EUri.php | 169 +++ extensions/EHttpClient/EUriHttp.php | 690 +++++++++ .../adapter/EHttpClientAdapterCurl.php | 492 +++++++ .../adapter/EHttpClientAdapterInterface.php | 87 ++ .../adapter/EHttpClientAdapterSocket.php | 342 +++++ .../adapter/EHttpClientAdapterStream.php | 56 + .../adapter/EHttpClientException.php | 11 + extensions/EHttpClient/adapter/EProxy.php | 278 ++++ extensions/EWebBrowser/EWebBrowser.php | 1111 +++++++++++++++ helpers/ECurrencyHelper/ECurrencyHelper.php | 262 ++++ helpers/EDownloadHelper/EDownloadHelper.php | 143 ++ helpers/EIniHelper/EIniHelper.php | 108 ++ helpers/EIniHelper/settings.ini | 11 + .../EABARoutingNumberValidator.php | 84 ++ validators /ECCValidator/ECCValidator.php | 209 +++ .../EConditionalValidator.php | 144 ++ validators /EIBANValidator/EIBANValidator.php | 204 +++ widgets/EDateRangePicker/EDateRangePicker.php | 112 ++ .../daterange/jquery.daterangepicker.js | 746 ++++++++++ .../assets/daterange/ui.daterangepicker.css | 103 ++ widgets/jqPrettyPhoto/jqPrettyPhoto.php | 42 + .../css/images/dark_rounded/btnNext.png | Bin 0 -> 1411 bytes .../css/images/dark_rounded/btnPrevious.png | Bin 0 -> 1442 bytes .../images/dark_rounded/contentPattern.png | Bin 0 -> 130 bytes .../images/dark_rounded/default_thumbnail.gif | Bin 0 -> 227 bytes .../css/images/dark_rounded/loader.gif | Bin 0 -> 2545 bytes .../css/images/dark_rounded/sprite.png | Bin 0 -> 4076 bytes .../css/images/dark_square/btnNext.png | Bin 0 -> 1411 bytes .../css/images/dark_square/btnPrevious.png | Bin 0 -> 1442 bytes .../css/images/dark_square/contentPattern.png | Bin 0 -> 121 bytes .../images/dark_square/default_thumbnail.gif | Bin 0 -> 227 bytes .../css/images/dark_square/loader.gif | Bin 0 -> 2545 bytes .../css/images/dark_square/sprite.png | Bin 0 -> 3507 bytes .../css/images/facebook/btnNext.png | Bin 0 -> 845 bytes .../css/images/facebook/btnPrevious.png | Bin 0 -> 828 bytes .../images/facebook/contentPatternBottom.png | Bin 0 -> 142 bytes .../images/facebook/contentPatternLeft.png | Bin 0 -> 137 bytes .../images/facebook/contentPatternRight.png | Bin 0 -> 136 bytes .../css/images/facebook/contentPatternTop.png | Bin 0 -> 142 bytes .../css/images/facebook/default_thumbnail.gif | Bin 0 -> 227 bytes .../css/images/facebook/loader.gif | Bin 0 -> 2545 bytes .../css/images/facebook/sprite.png | Bin 0 -> 4227 bytes .../css/images/light_rounded/btnNext.png | Bin 0 -> 1411 bytes .../css/images/light_rounded/btnPrevious.png | Bin 0 -> 1442 bytes .../light_rounded/default_thumbnail.gif | Bin 0 -> 227 bytes .../css/images/light_rounded/loader.gif | Bin 0 -> 2545 bytes .../css/images/light_rounded/sprite.png | Bin 0 -> 4099 bytes .../css/images/light_square/btnNext.png | Bin 0 -> 1411 bytes .../css/images/light_square/btnPrevious.png | Bin 0 -> 1442 bytes .../images/light_square/default_thumbnail.gif | Bin 0 -> 227 bytes .../css/images/light_square/loader.gif | Bin 0 -> 2545 bytes .../css/images/light_square/sprite.png | Bin 0 -> 3507 bytes .../prettyPhoto/css/prettyPhoto.css | 453 ++++++ widgets/jqPrettyPhoto/prettyPhoto/index.html | 151 ++ .../prettyPhoto/jquery.prettyPhoto.js | 1 + 114 files changed, 18686 insertions(+) create mode 100644 behaviors/EDateFormatBehavior/EDateFormatBehavior.php create mode 100644 behaviors/EJsonBehavior/EJsonBehavior.php create mode 100644 extensions/EFeed/EFeed.php create mode 100644 extensions/EFeed/EFeedItemAbstract.php create mode 100644 extensions/EFeed/EFeedItemAtom.php create mode 100644 extensions/EFeed/EFeedItemRSS1.php create mode 100644 extensions/EFeed/EFeedItemRSS2.php create mode 100644 extensions/EFeed/EFeedTag.php create mode 100644 extensions/EGMap/EGMap.php create mode 100644 extensions/EGMap/EGMapApiKeyList.php create mode 100644 extensions/EGMap/EGMapBase.php create mode 100644 extensions/EGMap/EGMapBounds.php create mode 100644 extensions/EGMap/EGMapClient.php create mode 100644 extensions/EGMap/EGMapControlPosition.php create mode 100644 extensions/EGMap/EGMapCoord.php create mode 100644 extensions/EGMap/EGMapDirection.php create mode 100644 extensions/EGMap/EGMapDirectionRenderer.php create mode 100644 extensions/EGMap/EGMapDirectionWayPoint.php create mode 100644 extensions/EGMap/EGMapEvent.php create mode 100644 extensions/EGMap/EGMapGeocodeTool.php create mode 100644 extensions/EGMap/EGMapGeocodedAddress.php create mode 100644 extensions/EGMap/EGMapInfoBox.php create mode 100644 extensions/EGMap/EGMapInfoWindow.php create mode 100644 extensions/EGMap/EGMapKMLService.php create mode 100644 extensions/EGMap/EGMapKeyDragZoom.php create mode 100644 extensions/EGMap/EGMapLatLonControl.php create mode 100644 extensions/EGMap/EGMapMarker.php create mode 100644 extensions/EGMap/EGMapMarkerClusterer.php create mode 100644 extensions/EGMap/EGMapMarkerImage.php create mode 100644 extensions/EGMap/EGMapMarkerWithLabel.php create mode 100644 extensions/EGMap/EGMapPoint.php create mode 100644 extensions/EGMap/EGMapSize.php create mode 100644 extensions/EGMap/assets/geoxml3.js create mode 100644 extensions/EGMap/assets/infobox_packed.js create mode 100644 extensions/EGMap/assets/keydragzoom_packed.js create mode 100644 extensions/EGMap/assets/latloncontrol.js create mode 100644 extensions/EGMap/assets/markerclusterer_packed.js create mode 100644 extensions/EGMap/assets/markers/m1.png create mode 100644 extensions/EGMap/assets/markers/m2.png create mode 100644 extensions/EGMap/assets/markers/m3.png create mode 100644 extensions/EGMap/assets/markers/m4.png create mode 100644 extensions/EGMap/assets/markers/m5.png create mode 100644 extensions/EGMap/assets/markerwithlabel_packed.js create mode 100644 extensions/EGMap/kml/EGMapKMLFeed.php create mode 100644 extensions/EGMap/kml/EGMapKMLIconStyle.php create mode 100644 extensions/EGMap/kml/EGMapKMLLineString.php create mode 100644 extensions/EGMap/kml/EGMapKMLLineStyle.php create mode 100644 extensions/EGMap/kml/EGMapKMLNode.php create mode 100644 extensions/EGMap/kml/EGMapKMLPlacemark.php create mode 100644 extensions/EGMap/kml/EGMapKMLPoint.php create mode 100644 extensions/EGMap/kml/EGMapKMLPolyStyle.php create mode 100644 extensions/EGMap/kml/EGMapKMLPolygon.php create mode 100644 extensions/EGeoIp/EGeoIP.php create mode 100644 extensions/EGeoNameService/EGeoNameService.php create mode 100644 extensions/EHttpClient/ECookieJar.php create mode 100644 extensions/EHttpClient/EHostnameValidator.php create mode 100644 extensions/EHttpClient/EHttpClient.php create mode 100644 extensions/EHttpClient/EHttpCookie.php create mode 100644 extensions/EHttpClient/EHttpResponse.php create mode 100644 extensions/EHttpClient/EUri.php create mode 100644 extensions/EHttpClient/EUriHttp.php create mode 100644 extensions/EHttpClient/adapter/EHttpClientAdapterCurl.php create mode 100644 extensions/EHttpClient/adapter/EHttpClientAdapterInterface.php create mode 100644 extensions/EHttpClient/adapter/EHttpClientAdapterSocket.php create mode 100755 extensions/EHttpClient/adapter/EHttpClientAdapterStream.php create mode 100644 extensions/EHttpClient/adapter/EHttpClientException.php create mode 100644 extensions/EHttpClient/adapter/EProxy.php create mode 100755 extensions/EWebBrowser/EWebBrowser.php create mode 100644 helpers/ECurrencyHelper/ECurrencyHelper.php create mode 100644 helpers/EDownloadHelper/EDownloadHelper.php create mode 100644 helpers/EIniHelper/EIniHelper.php create mode 100755 helpers/EIniHelper/settings.ini create mode 100644 validators /EABARoutingNumberValidator/EABARoutingNumberValidator.php create mode 100644 validators /ECCValidator/ECCValidator.php create mode 100644 validators /EConditionalValidator/EConditionalValidator.php create mode 100644 validators /EIBANValidator/EIBANValidator.php create mode 100644 widgets/EDateRangePicker/EDateRangePicker.php create mode 100755 widgets/EDateRangePicker/assets/daterange/jquery.daterangepicker.js create mode 100755 widgets/EDateRangePicker/assets/daterange/ui.daterangepicker.css create mode 100644 widgets/jqPrettyPhoto/jqPrettyPhoto.php create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnNext.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnPrevious.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/contentPattern.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/default_thumbnail.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/loader.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/sprite.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/btnNext.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/btnPrevious.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/contentPattern.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/default_thumbnail.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/loader.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/sprite.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnNext.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnPrevious.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternBottom.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternLeft.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternRight.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternTop.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/default_thumbnail.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/loader.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/sprite.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnNext.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnPrevious.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/default_thumbnail.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/loader.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/sprite.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/btnNext.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/btnPrevious.png create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/default_thumbnail.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/loader.gif create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/sprite.png create mode 100644 widgets/jqPrettyPhoto/prettyPhoto/css/prettyPhoto.css create mode 100755 widgets/jqPrettyPhoto/prettyPhoto/index.html create mode 100644 widgets/jqPrettyPhoto/prettyPhoto/jquery.prettyPhoto.js diff --git a/behaviors/EDateFormatBehavior/EDateFormatBehavior.php b/behaviors/EDateFormatBehavior/EDateFormatBehavior.php new file mode 100644 index 0000000..9e0920d --- /dev/null +++ b/behaviors/EDateFormatBehavior/EDateFormatBehavior.php @@ -0,0 +1,55 @@ + + */ +class EDateFormatBehavior extends CActiveRecordBehavior +{ + //.. array of columns that have dates to be converted + public $dateColumns = array(); + public $dateTimeColumns = array(); + + public $dateFormat = 'm/d/Y'; + public $dateTimeFormat = 'm/d/Y H:i'; + /** + * Convert from $dateFormat to UNIX timestamp dates before saving + */ + public function beforeSave($event) + { + $this->format($this->dateColumns, $this->dateFormat); + $this->format($this->dateTimeColumns, $this->dateTimeFormat); + return parent::beforeSave($event); + } + /** + * Converts UNIX timestamp dates to $dateFormat after read from database + */ + public function afterFind($event) + { + $this->format($this->dateColumns, $this->dateFormat, false); + $this->format($this->dateTimeColumns, $this->dateTimeFormat, false); + return parent::afterFind($event); + } + /** + * + * Formats to UNIX timestamp or $dateFormat as specified. Note that + * if using $dateFormat then assumed timestamp value + * @param array $columns the columns attributes to format + * @param string $format the format to convert the date to + * @param boolean $strtotime if boolean, will convert to UNIX timestamp + * @return void + */ + protected function format($columns, $format, $strtotime=true) + { + if(empty($columns)) return; + + foreach($this->getOwner()->getAttributes() as $key=>$value) + { + if(in_array($key, $columns) && !empty($value)) + { + $dt = $this->getOwner()->{$key}; + $this->getOwner()->{$key} = $strtotime ? strtotime($dt) : date($format,$dt); + } + } + } +} diff --git a/behaviors/EJsonBehavior/EJsonBehavior.php b/behaviors/EJsonBehavior/EJsonBehavior.php new file mode 100644 index 0000000..3f2ba6a --- /dev/null +++ b/behaviors/EJsonBehavior/EJsonBehavior.php @@ -0,0 +1,38 @@ +owner = $this->getOwner(); + + if (is_subclass_of($this->owner,'CActiveRecord')){ + + $attributes = $this->owner->getAttributes(); + $this->relations = $this->getRelated(); + + $jsonDataSource = array('jsonDataSource'=>array('attributes'=>$attributes,'relations'=>$this->relations)); + + return CJSON::encode($jsonDataSource); + } + return false; + } + private function getRelated() + { + $related = array(); + + $obj = null; + + $md=$this->owner->getMetaData(); + + foreach($md->relations as $name=>$relation){ + + $obj = $this->owner->getRelated($name); + + $related[$name] = $obj instanceof CActiveRecord ? $obj->getAttributes() : $obj; + } + + return $related; + } +} \ No newline at end of file diff --git a/extensions/EFeed/EFeed.php b/extensions/EFeed/EFeed.php new file mode 100644 index 0000000..87797d4 --- /dev/null +++ b/extensions/EFeed/EFeed.php @@ -0,0 +1,465 @@ + + * @package rss + * @uses CComponent, CUrlValidator + * @throws CException + */ +class EFeed extends CComponent{ + /** + * + * supported Feed formats + * + * @var string RSS1 + * @var string RSS2 + * @var string ATOM + */ + const RSS1 = 'RSS1'; + const RSS2 = 'RSS2'; + const ATOM = 'Atom'; + /** + * + * Holds all information and elements + * to generate the feed + * @var CMap $feedElements + */ + private $feedElements; + /** + * + * Holds stylesheet associated to the feed + * http://www.w3.org/TR/xml-stylesheet/#dt-xml-stylesheet + */ + private $stylesheets = array(); + /** + * + * Type of Feed Format + * @var string $type + */ + private $type; + /** + * Constructor + * + * @param constant the type constant (RSS1/RSS2/ATOM). + */ + function __construct($type = self::RSS2) + { + if( $type != self::RSS1 && $type != self::RSS2 && $type != self::ATOM ) + throw new CException( Yii::t('EFeed', 'Feed version not supported') ); + + $this->type = $type; + + // Initiate Feed holder + $this->feedElements = new CMap(); + + // Setting default value for essential channel elements + $this->addChannelTag('title', $this->type. ' Feed'); + $this->addChannelTag('link', 'http://www.ramirezcobos.com/' ); + + // Tag elements that we need to CDATA encode + $this->feedElements->add('CDATAEncoded', array('description', 'content:encoded', 'summary') ); + + } + /** + * + * Adds stylesheet support + * @param array $htmlOptions + */ + public function addStylesheetTag( $htmlOptions ) + { + if( !is_array( $htmlOptions )) + throw new CException( Yii::t( 'EFeed', __FUNCTION__.' parameter must be an array.' ) ); + + $this->stylesheets[] = ''; + } + /** + * + * Adds a channel element + * @param string $tag name of the element + * @param string $content of the element + */ + public function addChannelTag( $tag, $content ){ + if( null === $this->feedElements->itemAt('channels') ) + $this->feedElements->add('channels', new CMap()); + + $this->feedElements->itemAt('channels')->add( $tag, $content ); + } + /** + * + * Adds an array of channel elements + * They should be on the format: + *
+	 *    array('tagname'=>'tagcontent')
+	 * 
+ * @param unknown_type $tags + * @throws CException + */ + public function addChannelTagsArray( $tags ){ + if( !is_array( $tags ) ) + throw new CException( Yii::t( 'EFeed', __FUNCTION__.' parameter must be an array.' ) ); + + foreach( $tags as $tag=>$content ){ + $this->addChannelTag($tag, $content); + } + } + /** + * RSS1, RSS2 or ATOM + * @return EFeedItemAbstract Item + */ + public function createNewItem( ){ + + // create EFeedItem based on selected version type + $class = "EFeedItem".$this->type; + + return new $class; + } + /** + * Property setter the 'title' channel element + * + * @param string value of 'title' channel tag + */ + public function setTitle($title) + { + $this->addChannelTag('title', $title); + } + /** + * + * Property getter 'title' + * + * @return value of title channel tag | null + */ + public function getTitle( ){ + if( null !== $this->feedElements->itemAt('channels')){ + return $this->feedElements->itemAt('channels')->itemAt('title'); + } + return null; + } + /** + * Property setter 'description' channel element + * + * @param string value of 'description' channel tag + */ + public function setDescription($description) + { + $this->addChannelTag('description', $description); + } + /** + * + * Property getter 'description' + * + * @return value of description channel tag | null + */ + public function getDescription( ){ + if( null !== $this->feedElements->itemAt('channels')){ + return $this->feedElements->itemAt('channels')->itemAt('description'); + } + return null; + } + + /** + * Property setter 'link' channel element + * + * @param string value of 'link' channel tag + * @throws CException + */ + public function setLink($link) + { + $validator = new CUrlValidator(); + if(!$validator->validateValue($link)) + throw new CException( Yii::t('EFeed', $link. ' does not seem to be a valid URL') ); + + $this->addChannelTag('link', $link); + } + /** + * + * Property getter 'link' + * + * @return value of link channel tag | null + */ + public function getLink( ){ + if( null !== $this->feedElements->itemAt('channels')){ + return $this->feedElements->itemAt('channels')->itemAt('link'); + } + return null; + } + /** + * Set the 'image' channel element + * + * Cannot be used as property setter + * + * @param string title of image + * @param string link url of the image + * @param string path url of the image + */ + public function setImage($title, $link, $url) + { + $validator = new CUrlValidator(); + if(!$validator->validateValue($link)) + throw new CException( Yii::t('EFeed', $link. ' does not seem to be a valid URL') ); + + $this->addChannelTag('image', array('title'=>$title, 'link'=>$link, 'url'=>$url)); + } + /** + * + * Property getter image + * @return value of the image channel tag | null + */ + public function getImage(){ + if( null !== $this->feedElements->itemAt('channels')) + return $this->feedElements->itemAt('channels')->itemAt('image'); + return null; + } + /** + * + * Property setter the 'about' RSS 1.0 channel element + * + * @param string value of 'about' channel tag + */ + public function setRSS1ChannelAbout($url) + { + $validator = new CUrlValidator(); + if(!$validator->validateValue($url)) + throw new CException( Yii::t('EFeed', $url. ' does not seem to be a valid URL') ); + + $this->addChannelTag('ChannelAbout', $url); + + } + /** + * + * Property getter the 'about' RSS 1.0 channel + * @return value of 'about' channel tag | null + */ + public function getRSS1ChannelAbout(){ + if( null !== $this->feedElements->itemAt('channels')) + return $this->feedElements->itemAt('channels')->itemAt('ChannelAbout'); + return null; + } + /** + * + * Add a FeedItem to the main class + * + * @param object instance of EFeedItemAbstract class + */ + public function addItem(EFeedItemAbstract $item) + { + if( null === $this->feedElements->itemAt('items') ) + $this->feedElements->add( 'items', new CTypedList('EFeedItemAbstract') ); + + $this->feedElements->itemAt('items')->add( $item ); + } + + /** + * Generates an UUID + * + * @author Anis uddin Ahmad + * @param string an optional prefix + * @return string the formated uuid + */ + public static function uuid($key = null, $prefix = '') + { + $key = ($key == null)? uniqid(rand()) : $key; + $chars = md5($key); + $uuid = substr($chars,0,8) . '-'; + $uuid .= substr($chars,8,4) . '-'; + $uuid .= substr($chars,12,4) . '-'; + $uuid .= substr($chars,16,4) . '-'; + $uuid .= substr($chars,20,12); + + return $prefix . $uuid; + } + /** + * + * Generates the Feed + */ + public function generateFeed(){ + header("Content-type: text/xml"); + $this->renderHead(); + $this->renderChannels(); + $this->renderItems(); + $this->renderBottom(); + } + /** + * + * Prints the xml and rss namespace + * + */ + private function renderHead() + { + $head = '' . PHP_EOL; + if(!empty($this->stylesheets)) + $head .= implode (PHP_EOL, $this->stylesheets); + + if($this->type == self::RSS2) + { + $head .= CHtml::openTag('rss',array( + "version"=>"2.0", + "xmlns:content"=>"http://purl.org/rss/1.0/modules/content/", + "xmlns:wfw"=>"http://wellformedweb.org/CommentAPI/")).PHP_EOL; + } + elseif($this->type == self::RSS1) + { + $head .= CHtml::openTag('rdf:RDF',array( + "xmlns:rdf"=>"http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "xmlns"=>"http://purl.org/rss/1.0/", + "xmlns:dc"=>"http://purl.org/dc/elements/1.1/" + )).PHP_EOL; + } + else if($this->type == self::ATOM) + { + $head .= CHtml::openTag('feed', array("xmlns"=>"http://www.w3.org/2005/Atom")).PHP_EOL; + } + echo $head; + } + /** + * + * Prints the xml closing tags + * + */ + private function renderBottom() + { + if($this->type == self::RSS2) + { + echo CHtml::closeTag('channel'); + echo CHtml::closeTag('rss'); + } + elseif($this->type == self::RSS1) + { + echo CHtml::closeTag('rdf:RDF'); + } + else if($this->type == self::ATOM) + { + echo CHtml::closeTag('feed'); + } + + } + /** + * + * Prints the channels of the xml document + * @throws CException + */ + private function renderChannels(){ + + switch ($this->type) + { + case self::RSS2: + echo '' . PHP_EOL; + break; + case self::RSS1: + if(null !== $this->RSS1ChannelAbout ) + echo CHtml::tag('channel',array('rdf:about'=>$this->RSS1ChannelAbout )); + else + echo CHtml::tag('channel',array('rdf:about'=>$this->link)); + break; + } + + // Printing channel items + foreach ($this->feedElements->itemAt('channels') as $key => $value) + { + if($this->type == self::ATOM && $key == 'link') + { + // ATOM prints link element as href attribute + echo $this->makeNode($key,'',array('href'=>$value)); + // And add the id for ATOM + echo $this->makeNode('id',$this->uuid($value,'urn:uuid:')); + } + else + { + echo $this->makeNode($key, $value); + } + + } + + // RSS 1.0 have special tag with channel + if($this->type == self::RSS1) + { + if( null === $this->feedElements->itemAt('items') ) + throw new CException( Yii::t('EFeed', 'No items have been set') ); + + echo "" . PHP_EOL . "" . PHP_EOL; + + foreach ($this->feedElements->itemAt('items') as $item) + { + $tag = $item->link; + + if(null === $tag ) + throw new CException( Yii::t('EFeed', 'For RSS 1.0 specifications link element should be add per item') ); + + echo CHtml::tag('rdf:li',array('resource'=>$tag->content ),true).PHP_EOL; + } + echo "" . PHP_EOL . "" . PHP_EOL; + } + } + /** + * + * Prints feed items + * @throws CException + */ + private function renderItems() + { + if(null === $this->feedElements->itemAt('items')) + throw new CException( Yii::t('EFeed', 'No feed items configured') ); + + foreach ($this->feedElements->itemAt('items') as $item) + echo $item->getNode(); + } + /** + * + * Creates a single node as xml format + * + * @access private + * @param string name of the tag + * @param mixed tag value as string or array of nested tags in 'tagName' => 'tagValue' format + * @param array Attributes(if any) in 'attrName' => 'attrValue' format + * @return string formatted xml tag + */ + private function makeNode($tagName, $tagContent, $attributes = array()) + { + $node = ''; + + if( is_array($tagContent) && $this->type == self::RSS1 ) + $attributes['rdf:parseType']="Resource"; + + if( in_array($tagName, $this->feedElements->itemAt('CDATAEncoded')) ) + { + if($this->type == self::ATOM) + $attributes['type']="html"; + $node .= CHtml::openTag($tagName,$attributes). '$content) + $node .= $this->makeNode($tag, $content); + } + else + $node .= in_array($tagName, $this->feedElements->itemAt('CDATAEncoded')) ? $tagContent : CHtml::encode($tagContent); + + $node .= in_array($tagName, $this->feedElements->itemAt('CDATAEncoded'))? PHP_EOL.']]>' : ''; + + $node .= CHtml::closeTag($tagName); + + return $node.PHP_EOL; + + + } +} diff --git a/extensions/EFeed/EFeedItemAbstract.php b/extensions/EFeed/EFeedItemAbstract.php new file mode 100644 index 0000000..d2821e6 --- /dev/null +++ b/extensions/EFeed/EFeedItemAbstract.php @@ -0,0 +1,137 @@ + + * @package rss + */ +abstract class EFeedItemAbstract extends CComponent { + /** + * + * All element tags of this item collection + * @var CTypedMap('EFeedTag') + */ + protected $tags; + /** + * + * CDATAEncoded items for all different adapters + * @var array + */ + protected $CDATAEncoded = array('description', 'content:encoded', 'summary'); + /** + * + * Class constructor + */ + function __construct() + { + $this->tags = new CTypedMap('EFeedTag'); + } + /** + * + * Adds a tag to collection + * @param string $tag name of the element + * @param string $content of the element + * @param array $attributes of the tag + */ + public function addTag($tag, $content, $attributes = array()) + { + $this->tags->add($tag, new EFeedTag($tag, $content,(!is_array($attributes)? array() : $attributes))); + } + /** + * + * Returns specific tag by name + * @param string $name of the tag + * @return EFeedTag $tag + */ + public function getTag( $name ) + { + return $this->tags->itemAt( $name ); + } + /** + * + * Property title setter (thanks to CComponent) + *
+	 *    $feed->title = 'mytitle';
+	 * 
+ * @param string $title + */ + public function setTitle($title) { + $this->addTag('title', $title); + } + /** + * + * @return string title tag + */ + public function getTitle(){ + + return $this->tags->itemAt('title'); + } + /** + * + * Property description setter + * @param string $description + */ + public function setDescription( $description ){ + $this->addTag('description', $description); + } + /** + * + * @return string description tag + */ + public function getDescription( ){ + return $this->tags->itemAt('description'); + } + /** + * + * Property link setter + * @param string URI $link + */ + public function setLink($link) { + $validator = new CUrlValidator(); + $validator->pattern = '/(((f|ht){1}tp:\/\/)[-a-zA-Z0-9@:%_\+.~#?&\/\/=]+)/i'; + + if(!$validator->validateValue($link)) + throw new CException( Yii::t('EFeed', $link. ' does not seem to be a valid URL') ); + $this->addTag('link', $link); + } + /** + * + * @return link tag + */ + public function getLink(){ + return $this->tags->itemAt('link'); + } + /** + * + * Abstract property setter Ddte + * @param time() integer|string $date + */ + public abstract function setDate( $date ); + + /** + * + * Creates a single node as xml format + * @return string formatted xml tag + */ + public abstract function getNode(); + +} \ No newline at end of file diff --git a/extensions/EFeed/EFeedItemAtom.php b/extensions/EFeed/EFeedItemAtom.php new file mode 100644 index 0000000..1e2d5b5 --- /dev/null +++ b/extensions/EFeed/EFeedItemAtom.php @@ -0,0 +1,121 @@ + + * @package rss + * @uses CUrlValidator + * @throws CException + */ +class EFeedItemAtom extends EFeedItemAbstract { + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::setDescription() + */ + public function setDescription( $description ){ + $this->addTag('summary', $description ); + } + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::setDate() + */ + public function setDate( $date ){ + if(!is_numeric($date)) + $date = strtotime($date); + $date = date(DATE_ATOM,$date); + + $this->addTag('updated', $date); + } + /** + * + * Property getter date + */ + public function getDate(){ + return $this->tags->itemAt('updated'); + } + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::setLink() + */ + public function setLink( $link ){ + $validator = new CUrlValidator(); + if(!$validator->validateValue($link)) + throw new CException( Yii::t('EFeed', $link. ' does not seem to be a valid URL') ); + + $this->addTag('link','',array('href'=>$link)); + $this->addTag('id', EFeed::uuid($link,'urn:uuid:')); + } + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::getNode() + */ + public function getNode(){ + + $node = CHtml::openTag('entry').PHP_EOL; + + foreach( $this->tags as $tag ){ + $node .= $this->getElement($tag); + } + + $node .= CHtml::closeTag('entry').PHP_EOL; + + return $node; + } + /** + * + * @return a well formatted XML element + * @param EFeedTag $tag + */ + private function getElement( EFeedTag $tag ){ + + $element = ''; + + if(in_array($tag->name,$this->CDATAEncoded)) + { + $tag->attributes['type']="html"; + $element .= CHtml::openTag($tag->name,$tag->attributes); + $element .= 'name,$tag->attributes); + } + + if(is_array($tag->content)) + { + foreach ($tag->content as $tag => $content) + { + $tmpTag = new EFeedTag($tag, $content); + + $element .= $this->getElement( $tmpTag ); + } + } + else + { + $element .= (in_array($tag->name, $this->CDATAEncoded))? $tag->content : CHtml::encode($tag->content); + } + + $element .= (in_array($tag->name, $this->CDATAEncoded))? "]]>":""; + + $element .= CHtml::closeTag($tag->name).PHP_EOL; + + return $element; + } +} \ No newline at end of file diff --git a/extensions/EFeed/EFeedItemRSS1.php b/extensions/EFeed/EFeedItemRSS1.php new file mode 100644 index 0000000..fe07520 --- /dev/null +++ b/extensions/EFeed/EFeedItemRSS1.php @@ -0,0 +1,108 @@ + + * @package rss + * @uses CUrlValidator + * @throws CException + */ +class EFeedItemRSS1 extends EFeedItemAbstract{ + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::setDate() + */ + public function setDate( $date ){ + if(!is_numeric($date)) $date = strtotime( $date ); + + $date = date("Y-m-d", $date); + + $this->addTag( 'dc:date', $date ); + } + /** + * + * Property getter date + * @return date element | null + */ + public function getDate(){ + return $this->tags->itemAt('dc:date'); + } + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::getNode() + */ + public function getNode(){ + + if( null === $this->link || null === $this->link->content) + throw new CException( Yii::t('EFeed', 'Link Element is not set and it is required for RSS 1.0 to be used as about attribute of item') ); + + $node = CHtml::openTag('item', array('rdf:about'=>$this->link->content)).PHP_EOL; + + foreach( $this->tags as $tag ){ + $node .= $this->getElement($tag); + } + + $node .= CHtml::closeTag('item'); + + return $node.PHP_EOL; + } + /** + * + * @returns well formatted xml element + * @param EFeedTag $tag + */ + private function getElement( EFeedTag $tag ){ + + $element = ''; + + if(is_array($tag->content)) $tag->attributes['rdf:parseType']="Resource"; + + if(in_array($tag->name,$this->CDATAEncoded)) + { + $element .= CHtml::openTag($tag->name,$tag->attributes); + $element .= 'name,$tag->attributes); + } + $element .= PHP_EOL; + + if(is_array($tag->content)) + { + foreach ($tag->content as $tag => $content) + { + $tmpTag = new EFeedTag($tag, $content); + + $element .= $this->getElement( $tmpTag ); + } + } + else + { + $element .= (in_array($tag->name, $this->CDATAEncoded))? $tag->content : CHtml::encode($tag->content); + } + + $element .= (in_array($tag->name, $this->CDATAEncoded))? PHP_EOL.']]>':""; + + $element .= CHtml::closeTag($tag->name).PHP_EOL; + + return $element; + } +} \ No newline at end of file diff --git a/extensions/EFeed/EFeedItemRSS2.php b/extensions/EFeed/EFeedItemRSS2.php new file mode 100644 index 0000000..f9c1496 --- /dev/null +++ b/extensions/EFeed/EFeedItemRSS2.php @@ -0,0 +1,119 @@ + + * @package rss + * @uses CUrlValidator + * @throws CException + */ +class EFeedItemRSS2 extends EFeedItemAbstract{ + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::setDate() + */ + public function setDate( $date ){ + if(!is_numeric( $date )) $date = strtotime( $date ); + + $date = date( DATE_RSS, $date ); + + $this->addTag( 'pubDate', $date ); + } + /** + * + * Property getter date + * @return value of date | null + */ + public function getDate(){ + return $this->tags->itemAt('pubDate'); + } + /** + * (non-PHPdoc) + * @see EFeedItemAbstract::getNode() + */ + public function getNode(){ + + + $node = CHtml::openTag('item').PHP_EOL; + + foreach( $this->tags as $tag ){ + $node .= $this->getElement($tag); + } + + $node .= CHtml::closeTag('item'); + + return $node.PHP_EOL; + } + /** + * + * @returns well formatted xml element + * @param EFeedTag $tag + */ + private function getElement( EFeedTag $tag ){ + + $element = ''; + + if(in_array($tag->name,$this->CDATAEncoded)) + { + $element .= CHtml::openTag($tag->name,$tag->attributes); + $element .= 'name,$tag->attributes); + } + $element .= PHP_EOL; + + if(is_array($tag->content)) + { + foreach ($tag->content as $tag => $content) + { + $tmpTag = new EFeedTag($tag, $content); + + $element .= $this->getElement( $tmpTag ); + } + } + else + { + $element .= (in_array($tag->name, $this->CDATAEncoded))? $tag->content : CHtml::encode($tag->content); + } + + $element .= (in_array($tag->name, $this->CDATAEncoded))? PHP_EOL.']]>':""; + + $element .= CHtml::closeTag($tag->name).PHP_EOL; + + return $element; + } + /** + * + * Set the 'encloser' element of feed item + * + * @param string The url attribute of encloser tag + * @param string The length attribute of encloser tag + * @param string The type attribute of encloser tag + */ + public function setEncloser($url, $length, $type) + { + $attributes = array('url'=>$url, 'length'=>$length, 'type'=>$type); + + $this->addTag('enclosure','',$attributes); + } +} + \ No newline at end of file diff --git a/extensions/EFeed/EFeedTag.php b/extensions/EFeed/EFeedTag.php new file mode 100644 index 0000000..1703024 --- /dev/null +++ b/extensions/EFeed/EFeedTag.php @@ -0,0 +1,58 @@ + + * @link http://www.ramirezcobos.com/ + * + * + * THIS SOFTWARE IS PROVIDED BY THE 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. + */ + +/** + * EFeedTag is the class for the Feed Items + * + * + * @author Antonio Ramirez Cobos + * @version $Id: EFeedTag.php 1 2010-12-31 Antonio Ramirez Cobos $ + * @package rss + */ +class EFeedTag{ + /** + * Tag name + * @var string name + */ + public $name; + /** + * + * Tag content + * @var string content + */ + public $content; + /** + * + * Tag attributes array + * @var array attributes + */ + public $attributes = array(); + /** + * + * EFeedTag constructor + * @param string $name + * @param string $content + * @param array $attributes + */ + function __construct($name, $content, $attributes = array() ){ + $this->name = $name; + $this->content = $content; + $this->attributes = is_array($attributes)? $attributes: array(); + } +} diff --git a/extensions/EGMap/EGMap.php b/extensions/EGMap/EGMap.php new file mode 100644 index 0000000..591d479 --- /dev/null +++ b/extensions/EGMap/EGMap.php @@ -0,0 +1,1254 @@ + array('js' => array('markerwithlabel_packed.js'), 'flag' => false), + 'EGMapKeyDragZoom' => array('js' => array('keydragzoom_packed.js'), 'flag' => false), + 'EGMapMarkerClusterer' => array('js' => array('markerclusterer_packed.js'), 'flag' => false), + 'EGMapLatLonControl' => array('js' => array('latloncontrol.js'), 'flag' => false), + 'EGMapKMLService' => array('js' => array('geoxml3.js'), 'flag' => false), + 'EGMapInfoBox' => array('js'=> array('infobox_packed.js'), 'flag'=> false) + ); + + /** + * + * Folder reference to the registered plugin assets + * @var string + */ + private $pluginDir = null; + + /** + * + * HTML document Id + * @var string + */ + private $_containerId; + + /** + * + * Container HTML attributes + * @var array + */ + private $_htmlOptions = array(); + + /** + * + * Container CSS options + *
+	 * 	array('width'=>'512px','height'=>'512px');
+	 * 
+ * @var array + */ + private $_styleOptions = array('width' => '512px', 'height' => '512px'); + + /** + * + * default Google Map Options + * @var array + */ + protected $options = array( + // boolean If true, do not clear the contents of the Map div. + 'noClear ' => null, + // Enables/disables zoom and center on double click. true by default. + 'disableDoubleClickZoom' => null, + // string Color used for the background of the Map div. This color will be visible when tiles have not yet loaded as a user pans. + 'backgroundColor' => null, + // string The name or url of the cursor to display on a draggable object. + 'draggableCursor' => null, + // string The name or url of the cursor to display when an object is dragging. + 'draggingCursor' => null, + // boolean If false, prevents the map from being dragged. Dragging is enabled by default. + 'draggable' => null, + // boolean If true, enables scrollwheel zooming on the map. The scrollwheel is disabled by default. + 'scrollwheel' => null, + // boolean If false, prevents the map from being controlled by the keyboard. Keyboard shortcuts are enabled by default. + 'keyboardShortcuts' => null, + // LatLng The initial Map center. Required. + 'center' => null, + // number The initial Map zoom level. Required. + 'zoom' => null, + // The maximum zoom level which will be displayed on the map. If omitted, or set to + // null, the maximum zoom from the current map type is used instead. + 'maxZoom' => null, + // The minimum zoom level which will be displayed on the map. If omitted, or set to + // null, the minimum zoom from the current map type is used instead. + 'minZoom' => null, + // The enabled/disabled state of the zoom control. + // true by default + 'zoomControl' => null, + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#ZoomControlStyle + 'zoomControlStyle' => null, + // Of type named array + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#ZoomControlOptions + 'zoomControlOptions' => null, + // The initial enabled/disabled state of the Street View pegman control. + 'streetViewControl' => null, + // The initial display options for the Street View pegman control. + // Of type named array + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#streetViewControlOptions + 'streetViewControlOptions' => null, + // string The initial Map mapTypeId. Required. + 'mapTypeId' => self::TYPE_ROADMAP, + // boolean Enables/disables all default UI. May be overridden individually. + 'disableDefaultUI' => null, + // boolean The initial enabled/disabled state of the Map type control. + 'mapTypeControl' => null, + // MapTypeControl options The initial display options for the Map type control. + // Of type named array + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#MapTypeControlOptions + 'mapTypeControlOptions' => null, + // The enabled/disabled state of the pan control. + 'panControl' => null, + // The display options for the pan control. + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#PanControlOptions + 'panControlOptions' => null, + // boolean The initial enabled/disabled state of the scale control. + 'scaleControl' => null, + // ScaleControl options The initial display options for the scale control. + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#ScaleControlOptions + // Of type named array + 'scaleControlOptions' => null, + // boolean The initial enabled/disabled state of the navigation control. + 'navigationControl' => null, + // NavigationControl options The initial display options for the navigation control. + // http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/3.2/reference.html#NavigationControlOptions + // Of type named array + 'navigationControlOptions' => null + ); + + /** + * + * Where the map should be appended to + * (refer to registerMap Script) + * It can be any valid javascript #id identifier + */ + private $_appendTo = null; + + /** + * + * If enabled will hold a reference to a + * EGMapKeyDragZoom object + * @var EGMapKeyDragZoom + */ + private $_keyDrag = null; + + /** + * + * If enabled will hold a reference to a + * EGMapClusteredManager object + */ + private $_markerClusterer = null; + + /** + * + * If enabled will hold a reference to a + * EGMapLatLonControl object + */ + private $_latLonControl = null; + + /** + * + * If enabled will hold a reference to a + * EGMapKMLService object + */ + private $_kmlService = null; + + /** + * @todo replace following variables to + * a CMap object with CTypedList | CTypedMap Collections + * $resources = new CMap() + * $resources['markers'] = new CTypedList('EGMapMarker'); + * $resources['variables'] = new CMap(); + * $resources['events'] + */ + protected $resources; + + /** + * the interface to the Google Maps API web service + */ + protected $gMapClient = null; + + /** + * Constructs a Google Map PHP object + * + * @param array $options Google Map Options + * @param array $htmlOptions Container HTML attributes + */ + public function __construct($options=array(), $htmlOptions=array()) + { + + $this->resources = new CMap(); + + $this->setOptions($options); + $this->setHtmlOptions($htmlOptions); + + $this->gMapClient = new EGMapClient(); + } + + /** + * + * Sets the HTML attributes of the container + * @param array $options + */ + public function setHtmlOptions($options) + { + if (is_scalar($options)) + return; + $this->_htmlOptions = array_merge($this->_htmlOptions, $options); + } + + /** + * + * Returns the HTML attributes of the container + * @return array + */ + public function getHtmlOptions() + { + return $this->_htmlOptions; + } + + /** + * + * Sets Google Map Options + * @param array $options + * + */ + public function setOptions($options) + { + $this->options = CMap::mergeArray($this->options, $options); + } + + /** + * + * Returns the Google API key + * @see EGMapClient + * @return string $key + */ + public function getAPIKey() + { + return $this->getGMapClient()->getAPIKey(); + } + + /** + * + * Sets a Google API key for a specific domain + * @param string $domain + * @param string $key + */ + public function setAPIKey($domain, $key) + { + $this->getGMapClient()->setAPIKey($domain, $key, true); + } + + /** + * Gets an instance of the interface to the Google Map web geocoding service + * + * @return EGMapClient + */ + public function getGMapClient() + { + if (null === $this->gMapClient) + $this->gMapClient = new EGMapClient(); + + return $this->gMapClient; + } + + /** + * Sets an instance of the interface to the Google Map web geocoding service + * + * @param EGMapClient + */ + public function setGMapClient($gMapClient) + { + $this->gMapClient = $gMapClient; + } + + /** + * Geocodes an address + * @param string $address + * @return GMapGeocodedAddress + * @author Fabrice Bernhard + */ + public function geocode($address) + { + $address = trim($address); + + $gMapGeocodedAddress = new EGMapGeocodedAddress($address); + $accuracy = $gMapGeocodedAddress->geocode($this->getGMapClient()); + + if ($accuracy) + return $gMapGeocodedAddress; + + return null; + } + + /** + * Geocodes an address and returns additional normalized information + * @param string $address + * @return GMapGeocodedAddress + * @author Fabrice Bernhard + * @since 2010-12-22 Yii Modified Antonio Ramirez + */ + public function geocodeXml($address) + { + $address = trim($address); + + $gMapGeocodedAddress = new EGMapGeocodedAddress($address); + $gMapGeocodedAddress->geocodeXml($this->getGMapClient()); + + return $gMapGeocodedAddress; + } + + /** + * Returns the ID of the widget or generates a new one if requested. + * @param boolean $autoGenerate whether to generate an ID if it is not set previously + * @return string id of the widget. + * @author Antonio Ramirez + */ + public function getContainerId($autoGenerate=true) + { + if ($this->_containerId !== null) + return $this->_containerId; + else if ($autoGenerate) + return $this->_containerId = 'EGMapContainer' . parent::$_counter++; + } + + /** + * + * Sets content layer ID + * @param integer $id + * @author Antonio Ramirez + */ + public function setContainerId($id) + { + $this->_containerId = $id; + } + + /** + * + * Sets the id of the layer where the maps should be rendered + * @param string $id ie. #idcontainer + */ + public function appendMapTo($id) + { + if (substr(ltrim($id), 0, 1) != '#' && $id != 'body') + throw new CException(Yii::t('EGMap', 'The id of the layer doesnt seem a correct ID (not CSS selector)
Function: ' . __FUNCTION__)); + $this->_appendTo = $id; + } + + /** + * Defines one attributes of the div container + * Styles are defined differently + * @link $this->setStyles + * @param array $htmlOptions of attributes + * @author Antonio Ramirez + */ + public function setContainerOptions($htmlOptions) + { + if (is_scalar($htmlOptions)) + throw new CException(Yii::t('EGMap', 'setContainerOptions: $htmlOptions must be an array')); + + if (isset($htmlOptions['id'])) + $this->setContainerId($htmlOptions['id']); + $this->_htmlOptions = $htmlOptions; + } + + /** + * + * Returns the attribute options of the container + * @return array htmlOptions + * @author Antonio Ramirez + */ + public function getContainerOptions() + { + return $this->_htmlOptions; + } + + /** + * returns the Html for the Google map container + * @param Array $options Style options of the HTML container + * @return string $container + * @since 2010-12-22 Yii modified Antonio Ramirez + */ + public function getContainer($styles=array(), $attributes=array()) + { + $options = array_merge($this->_htmlOptions, array('id' => $this->getContainerId())); + if (!isset($options['style'])) + $options['style'] = ''; + + foreach ($this->_styleOptions as $style => $value) + { + $options['style'] .= $style . ':' . $value . ';'; + } + + return CHtml::tag('div', $options, '', true); + } + + /** + * + * @return string + * @author fabriceb + * @since 2009-08-20 + * @since 2011-01-21 Modified by Antonio Ramirez + * Improved algorithm + */ + public function optionsToJs() + { + return $this->encode($this->options); + } + + /** + * + * Registers the Javascript required for the Google map + * @param array $afterInit -javascript code to be rendered after init call + * @param string $language -preferred language setting for the results + * @param string $region -top level geographic domain + * @param ClientScript::CONSTANT $position -where to render the script + * @since 2010-12-22 Antonio Ramirez (inspired by sfGMap Plugin of Fabriceb) + * @since 2011-01-09 Antonio Ramirez + * removed deprecated initialization procedures //$init_events[] = $this->getIconsJs(); + * @since 2011-01-22 Antonio Ramirez + * Added support for key drag and marker clusterer plugin + */ + public function registerMapScript($afterInit=array(), $language = null, $region = null, $position = CClientScript::POS_LOAD) + { + // TODO: include support in the future + $params = 'sensor=false'; + + if ($language !== null) + $params .= '&language=' . $language; + if ($region !== null) + $params .= '®ion=' . $region; + + CGoogleApi::init(); + CGoogleApi::register('maps', '3', array('other_params' => $params)); + + $this->registerPlugins(); + + $js = ''; + + $init_events = array(); + if (null !== $this->_appendTo) + { + $init_events[] = "$('{$this->getContainer()}').appendTo('{$this->_appendTo}');" . PHP_EOL; + } + $init_events[] = 'var mapOptions = ' . $this->encode($this->options) . ';' . PHP_EOL; + $init_events[] = $this->getJsName() . ' = new google.maps.Map(document.getElementById("' . $this->getContainerId() . '"), mapOptions);' . PHP_EOL; + + + // add some more events + $init_events[] = $this->getEventsJs(); + $init_events[] = $this->getMarkersJs(); + $init_events[] = $this->getDirectionsJs(); + $init_events[] = $this->getPluginsJs(); + + if (is_array($afterInit)) + { + foreach ($afterInit as $ainit) + $init_events[] = $ainit; + } + if ($this->getGlobalVariable($this->getJsName() . '_info_window')) + $init_events[] = $this->getJsName() . '_info_window=new google.maps.InfoWindow();'; + if ($this->getGlobalVariable($this->getJsName() . '_info_box') && $this->resources->itemAt('infobox_config')) + $init_events[] = $this->getJsName (). '_info_box=new InfoBox('. + $this->resources->itemAt('infobox_config').');'; + + // declare the Google Map Javascript object as global + $this->addGlobalVariable($this->getJsName(), 'null'); + + $js = $this->getGlobalVariables(); + + Yii::app()->getClientScript()->registerScript('EGMap_' . $this->getJsName(), $js, CClientScript::POS_HEAD); + + $js = 'function ' . $this->_containerId . '_init(){' . PHP_EOL; + foreach ($init_events as $init_event) + { + if ($init_event) + { + $js .= $init_event . PHP_EOL; + } + } + $js .= ' + } google.maps.event.addDomListener(window, "load",' . PHP_EOL . $this->_containerId . '_init);' . PHP_EOL; + + Yii::app()->getClientScript()->registerScript($this->_containerId . time(), $js, CClientScript::POS_END); + } + + /** + * @return string javascript code from plugins + */ + public function getPluginsJs() + { + $return = ''; + if (null !== $this->_markerClusterer) + $return .= $this->_markerClusterer->toJs($this->getJsName()); + if (null !== $this->_keyDrag) + $return .= $this->_keyDrag->toJs($this->getJsName()); + if (null !== $this->_latLonControl) + $return .= $this->_latLonControl->toJs($this->getJsName()); + if (null !== $this->_kmlService) + $return .= $this->_kmlService->toJs($this->getJsName()); + return $return; + } + + /** + * + * Enables LatLonControl plugin + * + */ + public function enableKMLService($url, $localhost = false) + { + if (true === $localhost) + $this->registerPlugin('EGMapKMLService'); + $this->_kmlService = new EGMapKMLService($url); + } + + /** + * + * Disables LatLonControl plugin + */ + public function disableKMLService() + { + $this->registerPlugin('EGMapKMLService', false); + $this->_kmlService = null; + } + + /** + * + * Enables LatLonControl plugin + * + */ + public function enableLatLonControl() + { + $this->registerPlugin('EGMapLatLonControl'); + $this->_latLonControl = new EGMapLatLonControl(); + } + + /** + * + * Disables LatLonControl plugin + */ + public function disableLatLonControl() + { + $this->registerPlugin('EGMapLatLonControl', false); + $this->_latLonControl = null; + } + + /** + * + * Enables Marker Clusterer Plugin + * @param EGMapMarkerClusterer $markerclusterer + * @author Antonio Ramirez + */ + public function enableMarkerClusterer(EGMapMarkerClusterer $markerclusterer) + { + $this->registerPlugin('EGMapMarkerClusterer'); + $this->_markerClusterer = $markerclusterer; + } + + /** + * + * Disables Marker Clusterer Plugin + * @author Antonio Ramirez + */ + public function disableMarkerClusterer() + { + $this->registerPlugin('EGMapMarkerClusterer'); + $this->_markerClusterer = null; + } + + /** + * + * Enables Key drag Zoom plugin + * @param EGMapKeyDragZoom $dragzoom + * @author Antonio Ramirez + */ + public function enableKeyDragZoom(EGMapKeyDragZoom $dragzoom) + { + $this->registerPlugin('EGMapKeyDragZoom'); + $this->_keyDrag = $dragzoom; + } + + /** + * + * Disables Key Drag Zoom Plugin + */ + public function disableKeyDragZoom() + { + $this->registerPlugin('EGMapKeyDragZoom', false); + $this->_keyDrag = null; + } + + /** + * + * Lazy Programmer's function to register the javascript needed and display HTML + * map container + * @param array $afterInit -javascript code to be rendered after init call + * @param string $language -preferred language setting for the results + * @param string $region -top level geographic domain + * @param ClientScript::CONSTANT $position -where to render the script + */ + public function renderMap($afterInit=array(), $language = null, $region = null, $position = CClientScript::POS_LOAD) + { + + $this->registerMapScript($afterInit, $language, $region, $position); + if (null === $this->_appendTo) + echo $this->getContainer(); + } + + /** + * @param EGMapMarker $marker a marker to be put on the map + * @since 2011-01-11 added support for global infowindow + * @since 2011-01-22 added support for EGMapMarkerWithLabel plugin + * @since 2011-01-23 fixed info window shared + */ + public function addMarker(EGMapMarker $marker) + { + if (null === $this->resources->itemAt('markers')) + $this->resources->add('markers', new CTypedList('EGMapMarker')); + if ($marker->getHtmlInfoWindow() && $marker->htmlInfoWindowShared() && !$this->getGlobalVariable($this->getJsName() . '_info_window')) + $this->addGlobalVariable($this->getJsName() . '_info_window', 'null'); + if ($marker->getHtmlInfoBox() && $marker->htmlInfoBoxShared() && !$this->getGlobalVariable($this->getJsName() . '_info_box')) + { + $this->addGlobalVariable($this->getJsName() . '_info_box', 'null'); + $this->resources->add('infobox_config',$marker->getHtmlInfoBox()->getEncodedOptions()); + $this->registerPlugin('EGMapInfoBox'); + } + if ($marker instanceof EGMapMarkerWithLabel && !$this->pluginRegistered('EGMapMarkerWithLabel')) + $this->registerPlugin('EGMapMarkerWithLabel'); + $this->resources->itemAt('markers')->add($marker); + } + + /** + * @param EGMapMarker[] $markers marker to be put on the map + * @since 2011-01-22 Antonio Ramirez + * Added support for EGMapMarkerWithLabel plugin + */ + public function setMarkers(CTypedList $markers) + { + foreach ($markers as $marker) + { + if (!$marker instanceof EGMapMarker) + throw new CException(Yii::t('EGMap', 'Markers collection must be of base class EGMapMarker')); + if ($marker instanceof EGMapMarkerWithLabel && !$this->pluginRegistered('EGMapMarkerWithLabel')) + $this->registerPlugin('EGMapMarkerWithLabel'); + } + $this->resources->add('markers', $markers); + } + + /** + * @param EGMapEvent $event an event to be attached to the map + */ + public function addEvent(EGMapEvent $event) + { + if (null === $this->resources->itemAt('events')) + $this->resources->add('events', new CTypedList('EGMapEvent')); + + $this->resources->itemAt('events')->add($event); + } + + /** + * $directions getter + * + * @return array $directions + * @author Vincent Guillon + * @since 2009-11-13 17:18:29 + */ + public function getDirections() + { + + return $this->resources->itemAt('directions'); + } + + /** + * $directions setter + * + * @param CTypedList $directions + * @author Vincent Guillon + * @since 2009-11-13 17:21:18 + */ + public function setDirections($directions = null) + { + + if ($directions instanceof CTypedList) + $this->resources->add('directions', $directions); + } + + /** + * Add direction to list ($this->directions) + * + * @param EGMapDirection $directions + * @author Antonio Ramirez + */ + public function addDirection(EGMapDirection $direction) + { + if (null === $this->resources->itemAt('directions')) + $this->resources->add('directions', new CTypedList('EGMapDirection')); + + $this->resources->itemAt('directions')->add($direction); + } + + /** + * Returns the javascript string which defines the markers + * @return string + * @since 2011-01-22 modified Antonio Ramirez + * Added support for marker clusterer + */ + public function getMarkersJs() + { + $return = ''; + if (null !== $this->resources->itemAt('markers')) + { + foreach ($this->resources->itemAt('markers') as $marker) + { + $return .= $marker->toJs($this->getJsName()); + if (null !== $this->_markerClusterer) + $this->_markerClusterer->addMarker($marker); + $return .= "\n "; + } + } + return $return; + } + + /** + * Returns the javascript string which defines events linked to the map + * + * @return string + * @since 2011-01-21 handles different type of events now + */ + public function getEventsJs() + { + + $return = ''; + if (null !== $this->resources->itemAt('events')) + { + foreach ($this->resources->itemAt('events') as $event) + { + $return .= $event->toJs($this->getJsName()); + $return .= "\n"; + } + } + return $return; + } + + /** + * Get the directions javascript code + * + * @return string $js_code + * @author Antonio Ramirez + */ + public function getDirectionsJs() + { + $js_code = ''; + if (null !== $this->resources->itemAt('directions')) + { + foreach ($this->resources->itemAt('directions') as $direction) + { + $js_code .= $direction->toJs($this->getJsName()); + $js_code .= "\n "; + } + } + return $js_code; + } + + /** + * + * Adds global variables to be set before init function + * @param string $name + * @param mixed $value + */ + public function addGlobalVariable($name, $value='null') + { + if (null === $this->resources->itemAt('variables')) + $this->resources->add('variables', new CMap()); + + $this->resources->itemAt('variables')->add($name, $value); + } + + /** + * + * @return global variable if set + */ + public function getGlobalVariable($name) + { + if (null === $this->resources->itemAt('variables')) + return null; + + return $this->resources->itemAt('variables')->itemAt($name); + } + + /** + * + * Removes a global variable + * @param string $name of the variable to remove + */ + public function removeGlobalVariable($name) + { + if (null === $this->resources->itemAt('variables')) + return; + + $this->resources->itemAt('variables')->remove($name); + } + + /** + * + * @return string global variables in JS format + */ + public function getGlobalVariables() + { + $return = ''; + if (null !== $this->resources->itemAt('variables')) + { + foreach ($this->resources->itemAt('variables') as $name => $value) + { + $return .=' + var ' . $name . ' = ' . $value . ';'; + } + } + return $return; + } + + /** + * Defines one style of the div container + * @param string $style_tag name of css tag + * @param string $style_value value of css tag + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function setContainerStyle($style_tag, $style_value) + { + if (!is_array($this->_styleOptions)) + $this->_styleOptions = array(); + + $this->_styleOptions = array_merge($this->_styleOptions, array($style_tag => $style_value)); + } + + /** + * + * Gets one style of the Google Map div + * @param string $style_tag name of css tag + * @since 2010-12-22 modified Antonio Ramirez + */ + public function getContainerStyle($style_tag) + { + if (isset($this->_styleOptions[$style_tag])) + return $this->_styleOptions[$style_tag]; + return false; + } + + /** + * Sets the center of the map at the beginning + * + * @param float $lat + * @param float $lng + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function setCenter($lat=null, $lng=null) + { + $coord = new EGMapCoord($lat, $lng); + $this->options['center'] = $coord; + } + + /** + * + * @return EGMapCoord + */ + public function getCenterCoord() + { + return $this->options['center']; + } + + /** + * + * @return float Latitude + */ + public function getCenterLat() + { + + return isset($this->options['center']) ? $this->getCenterCoord()->getLatitude() : null; + } + + /** + * + * @return float Longitude + */ + public function getCenterLng() + { + return isset($this->options['center']) ? $this->getCenterCoord()->getLongitude() : null; + } + + /** + * gets the width of the map in pixels according to container style + * @return integer + * @since 2010-12-22 code reduction Yii Antonio Ramirez + */ + public function getWidth() + { + if (substr($this->getContainerStyle('width'), -2, 2) != 'px') + return false; + return intval(substr($this->getContainerStyle('width'), 0, -2)); + } + + /** + * gets the width of the map in pixels according to container style + * @return integer + * @since 2010-12-22 code reduction Antonio Ramirez + */ + public function getHeight() + { + if (substr($this->getContainerStyle('height'), -2, 2) != 'px') + return false; + + return intval(substr($this->getContainerStyle('height'), 0, -2)); + } + + /** + * sets the width of the map in pixels + * + * @param integer | string + */ + public function setWidth($width) + { + if (is_numeric($width)) + { + $width = $width . 'px'; + } + $this->setContainerStyle('width', $width); + } + + /** + * sets the width of the map in pixels + * + * @param integer | string + */ + public function setHeight($height) + { + if (is_numeric($height)) + { + $height = $height . 'px'; + } + $this->setContainerStyle('height', $height); + } + + /** + * Returns the URL of a static version of the map (when JavaScript is not active) + * Supports only markers and basic parameters: center, zoom, size. + * @param string $map_type = 'mobile' + * @param string $hl Language (fr, en...) + * @return string URL of the image + * @author Laurent Bachelier + * @since 2010-12-22 inserted http_build_query modified Antonio Ramirez + */ + public function getStaticMapUrl($maptype='mobile', $hl='es') + { + $params = array( + 'maptype' => $maptype, + 'zoom' => $this->getZoom(), + 'key' => $this->getAPIKey(), + 'center' => $this->getCenterLat() . ',' . $this->getCenterLng(), + 'size' => $this->getWidth() . 'x' . $this->getHeight(), + 'hl' => $hl, + 'markers' => $this->getMarkersStatic() + ); + $pairs = array(); + + $params = http_build_query($params); + + return 'http://maps.google.com/staticmap?' . $params; //implode('&',$pairs); + } + + /** + * Returns the static code to create markers + * @return string + * @author Laurent Bachelier + * @since 2010-12-22 Yii modified Antonio Ramirez + */ + protected function getMarkersStatic() + { + $markers_code = array(); + if (null !== $this->resources->itemAt('markers')) + { + foreach ($this->resources->itemAt('markers') as $marker) + { + $markers_code[] = $marker->getMarkerStatic(); + } + } + return implode('|', $markers_code); + } + + /** + * + * calculates the center of the markers linked to the map + * + * @return EGMapCoord + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function getMarkersCenterCoord() + { + if (null === $this->resources->itemAt('markers')) + throw new CException(Yii::t('EGMap', 'At least one more marker is necessary for getMarkersCenterCoord to work')); + //todo: check for markers existence + return EGMapMarker::getCenterCoord($this->resources->itemAt('markers')); + } + + /** + * sets the center of the map at the center of the markers + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function centerOnMarkers() + { + $center = $this->getMarkersCenterCoord(); + + $this->setCenter($center->getLatitude(), $center->getLongitude()); + } + + /** + * + * calculates the zoom which fits the markers on the map + * + * @param integer $margin a scaling factor around the smallest bound + * @return integer $zoom + * @author fabriceb + * @since 2009-05-02 + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function getMarkersFittingZoom($margin = 0, $default_zoom = 14) + { + if (null === $this->resources->itemAt('markers')) + throw new CException(Yii::t('EGMap', 'At least one more marker is necessary for getMarkersFittingZoom to work')); + //todo check for markers existence + $bounds = EGMapBounds::getBoundsContainingMarkers($this->resources->itemAt('markers'), $margin); + + return $bounds->getZoom(min($this->getWidth(), $this->getHeight()), $default_zoom); + } + + /** + * sets the zoom of the map to fit the markers (uses mercator projection to guess the size in pixels of the bounds) + * WARNING : this depends on the width in pixels of the resulting map + * + * @param integer $margin a scaling factor around the smallest bound + * @author fabriceb + * @since 2009-05-02 + */ + public function zoomOnMarkers($margin = 0, $default_zoom = 14) + { + $this->options['zoom'] = $this->getMarkersFittingZoom($margin, $default_zoom); + } + + /** + * sets the zoom and center of the map to fit the markers (uses mercator projection to guess the size in pixels of the bounds) + * + * @param integer $margin a scaling factor around the smallest bound + * @author fabriceb + * @since 2009-05-02 + */ + public function centerAndZoomOnMarkers($margin = 0, $default_zoom = 14) + { + $this->centerOnMarkers(); + $this->zoomOnMarkers($margin, $default_zoom); + } + + /** + * + * @return EGMapBounds + * @author fabriceb + * @since Jun 2, 2009 fabriceb + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public function getBoundsFromCenterAndZoom() + { + return EGMapBounds::getBoundsFromCenterAndZoom($this->getCenterCoord(), $this->zoom, $this->getWidth(), $this->getHeight()); + } + + /** + * backwards compatibility + * @param string[] $api_keys + * @return string + * @author fabriceb + * @since Jun 17, 2009 fabriceb + * @since 2010-12-22 modified for Yii Antonio Ramirez + */ + public static function guessAPIKey($api_keys = null) + { + return EGMapClient::guessAPIKey($api_keys); + } + + /** + * + * Loops through the plugins and registers its required + * assets + * @author Antonio Ramirez + */ + private function registerPlugins() + { + $assetDir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR; + $assetUrl = Yii::app()->assetManager->publish($assetDir); + + + $cs = Yii::app()->getClientScript(); + + foreach ($this->plugins as $p) + { + + if ($p['flag']) + { + foreach ($p['js'] as $js) + $cs->registerScriptFile($assetUrl . "/" . $js, CClientScript::POS_END); + } + } + } + + /** + * + * Flags a plugin to register its assets + * @param string $plugin name + * @param boolean $register + */ + private function registerPlugin($plugin, $register=true) + { + $this->plugins[$plugin]["flag"] = $register; + } + + /** + * + * Checks whether a plugin has been flagged to be + * registered or not + * @param string $plugin name + * @return boolean true|false + */ + private function pluginRegistered($plugin) + { + return $this->plugins[$plugin]["flag"]; + } + + /** + * + * Encodes an option array into + * appropiate Javascript object + * representation + * @param mixed $value + * @author Antonio Ramirez + */ + public static function encode($value) + { + + if (is_string($value)) + { + if (strpos($value, 'js:') === 0) + return substr($value, 3); + else + return $value; + } + else if ($value === null) + return 'null'; + else if (is_bool($value)) + return $value ? 'true' : 'false'; + else if (is_integer($value)) + return "$value"; + else if (is_float($value)) + { + if ($value === -INF) + return 'Number.NEGATIVE_INFINITY'; + else if ($value === INF) + return 'Number.POSITIVE_INFINITY'; + else + return rtrim(sprintf('%.16F', $value), '0'); // locale-independent representation + } + else if (is_object($value)) + { + if (method_exists($value, 'toJs')) + return $value->toJs(); + return self::encode(get_object_vars($value)); + } + else if (is_array($value)) + { + $es = array(); + if (($n = count($value)) > 0 && array_keys($value) !== range(0, $n - 1)) + { + + foreach ($value as $k => $v) + { + if (null === $v) + continue; + $es[] = $k . ":" . self::encode($v); + } + + return '{' . implode(',' . PHP_EOL, $es) . '}'; + } + else + { + foreach ($value as $v) + $es[] = self::encode($v); + return '[' . implode(',', $es) . ']'; + } + } + else + return ''; + } + +} diff --git a/extensions/EGMap/EGMapApiKeyList.php b/extensions/EGMap/EGMapApiKeyList.php new file mode 100644 index 0000000..b6fbfb5 --- /dev/null +++ b/extensions/EGMap/EGMapApiKeyList.php @@ -0,0 +1,98 @@ +addAPIKey( 'localhost', $this->_default ); + + if( $domain != null && $key != null ) + $this->add( $domain, $key ); + } + /** + * + * Adds a Google API key to collection + * @param string $domain + * @param string $key + */ + public function addAPIKey( $domain , $key ){ + if( null === $this->_keys ) + $this->_keys = new CMap(); + + $this->_keys->add( $domain, $key ); + } + /** + * + * Returns Google API key if found in collection + * @param string $domain + * @return string Google API key + */ + public function getAPIKeyByDomain( $domain ) + { + if( !$this->_keys->contains( $domain ) ) + return false; + return $this->_keys->itemAt( $domain ); + } + /** + * Returns and google api key by domain name discovery + * @return Google API key + */ + public static function guessAPIKey( ) + { + if (isset($_SERVER['SERVER_NAME'])) + { + return $this->getAPIKeyByDomain( $_SERVER['SERVER_NAME'] ); + } + else if (isset($_SERVER['HTTP_HOST'])) + { + return $this->getAPIKeyByDomain( $_SERVER['HTTP_HOST'] ); + } + return $this->getAPIKeyByDomain('localhost'); + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapBase.php b/extensions/EGMap/EGMapBase.php new file mode 100644 index 0000000..bedd417 --- /dev/null +++ b/extensions/EGMap/EGMapBase.php @@ -0,0 +1,134 @@ + + * $this->propertyName=$value; + * $this->setName($value) + * + * @param string $name the property name + * @param mixed $value + */ + public function __set($name, $value) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) + return $this->$setter($value); + elseif (array_key_exists($name, $this->options)) + { + return $this->options[$name] = $value; + } + if (method_exists($this, 'get' . $name)) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" is read only.', array('{class}' => get_class($this), '{property}' => $name))); + else + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" is not defined.', array('{class}' => get_class($this), '{property}' => $name))); + } + + /** + * + * Returns a property value, an event handler list or a behavior based on its name. + * Do not call this method. This is a PHP magic method that we override + * to allow using the following syntax to read a property + *
+	 * $value=$component->propertyName;
+	 * $value=$component->getPropertyName;
+	 * 
+ * @param string $name + * @throws CException + */ + public function __get($name) + { + $getter = 'get' . ucfirst($name); + + if (method_exists($this, $getter)) + return $this->$getter(); + if (array_key_exists($name, $this->options)) + { + return $this->options[$name]; + } + + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" is not defined.', array('{class}' => get_class($this), '{property}' => $name))); + } + + /** + * Checks if a property value is null. + * Do not call this method. This is a PHP magic method that we override + * to allow using isset() to detect if a component property is set or not. + * + * @param string $name name of the property + */ + public function __isset($name) + { + $getter = 'get' . ucfirst($name); + if (method_exists($this, $getter)) + return $this->$getter() !== null; + + return isset($this->options[$name]); + } + + /** + * Sets a component property to be null. + * Do not call this method. This is a PHP magic method that we override + * to allow using unset() to set a component property to be null. + * @param string $name the property name or the event name + * @throws CException if the property is read only or not exists. + */ + public function __unset($name) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) + return $this->$setter(null); + else if (isset($this->option[$name])) + return $this->option[$name] = null; + else if (method_exists($this, 'get' . $name)) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" is read only.', array('{class}' => get_class($this), '{property}' => $name))); + else + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" is not defined.', array('{class}' => get_class($this), '{property}' => $name))); + } + + /** + * @return string Javascript name of the renderer service + * @author Antonio Ramirez + * @since 2011-01-24 + */ + public function getJsName($autoGenerate=true) + { + if ($this->js_name !== null) + return $this->js_name; + else if ($autoGenerate) + return $this->js_name = get_class($this) . self::$_counter++; + } + + /** + * + * Sets the Javascript name of the renderer service + * @param string $name + */ + public function setJsName($name) + { + + $this->js_name = $name; + } + + /** + * @return array $options + * @author Antonio Ramirez + * @since 2011-01-25 + */ + public function getOptions() + { + return $this->options; + } + +} + diff --git a/extensions/EGMap/EGMapBounds.php b/extensions/EGMap/EGMapBounds.php new file mode 100644 index 0000000..b74b08d --- /dev/null +++ b/extensions/EGMap/EGMapBounds.php @@ -0,0 +1,439 @@ +sw = $sw; + $this->ne = $ne; + } + /** + * + * @return EGMapCoord object + */ + public function getNorthEast() + { + + return $this->ne; + } + /** + * + * @return EGMapCoord object + */ + public function getSouthWest() + { + return $this->sw; + } + /** + * + * Creates EGMapCoords (bounds) from a string representation + * of Lat,Lon values + * @param string $string ((48.82415805606007,%202.308330535888672),%20(48.867086142850226,%202.376995086669922)) + */ + static public function createFromString($string) + { + preg_match('/\(\((.*?)\), \((.*?)\)\)/',$string,$matches); + if (count($matches)==3) + { + $sw = EGMapCoord::createFromString($matches[1]); + $ne = EGMapCoord::createFromString($matches[2]); + if ( !is_null($sw) && !is_null($ne)) + { + + return new EGMapBounds($sw,$ne); + } + + return null; + } + } + + /** + * Google String representations + * + * @return string + * @author fabriceb + * @since Feb 17, 2009 fabriceb + */ + public function __toString() + { + + return '(('.$this->getSouthWest()->getLatitude().', '.$this->getSouthWest()->getLongitude().'), ('.$this->getNorthEast()->getLatitude().', '.$this->getNorthEast()->getLongitude().'))'; + } + + + /** + * Get the latitude of the center of the zone + * + * @return integer + * @author fabriceb + * @since 2008-12-03 + */ + public function getCenterLat() + { + if (is_null($this->getSouthWest()) || is_null($this->getNorthEast())) + { + + return null; + } + + return floatval(($this->getSouthWest()->getLatitude()+$this->getNorthEast()->getLatitude())/2); + } + + /** + * Get the longitude of the center of the zone + * + * @return integer + * @author fabriceb + * @since 2008-12-03 + */ + public function getCenterLng() + { + if (is_null($this->getSouthWest()) || is_null($this->getNorthEast())) + { + + return null; + } + + return floatval(($this->getSouthWest()->getLongitude()+$this->getNorthEast()->getLongitude())/2); + } + + /** + * Get the coordinates of the center of the zone + * + * @return EGMapCoord + * @author fabriceb + * @since 2008-12-03 + */ + public function getCenterCoord() + { + + return new EGMapCoord($this->getCenterLat(), $this->getCenterLng()); + } + + /** + * Hauteur du carré + * + * @return float + * @author fabriceb + * @since Feb 17, 2009 fabriceb + */ + public function getHeight() + { + + return abs($this->getNorthEast()->getLatitude()-$this->getSouthWest()->getLatitude()); + } + + /** + * Largeur du carré + * + * @return float + * @author fabriceb + * @since Feb 17, 2009 fabriceb + */ + public function getWidth() + { + + return abs($this->getNorthEast()->getLongitude()-$this->getSouthWest()->getLongitude()); + } + + /** + * Does a homthety transformtion on the bounds, centered on the center of the bounds + * + * @param float $factor + * @return EGMapBounds $bounds + * @author fabriceb + * @since Feb 17, 2009 fabriceb + */ + public function getHomothety($factor) + { + $bounds = new EGMapBounds(); + $lat = $this->getCenterLat(); + $lng = $this->getCenterLng(); + $bounds->getNorthEast()->setLatitude($factor*$this->getNorthEast()->getLatitude()+$lat*(1-$factor)); + $bounds->getSouthWest()->setLatitude($factor*$this->getSouthWest()->getLatitude()+$lat*(1-$factor)); + $bounds->getNorthEast()->setLongitude($factor*$this->getNorthEast()->getLongitude()+$lng*(1-$factor)); + $bounds->getSouthWest()->setLongitude($factor*$this->getSouthWest()->getLongitude()+$lng*(1-$factor)); + + return $bounds; + } + + /** + * gets zoomed out bounds + * + * @param integer $zoom_coef + * @return EGMapBounds + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public function getZoomOut($zoom_coef) + { + if ($zoom_coef > 0) + { + $bounds = $this->getHomothety(pow(2,$zoom_coef)); + + return $bounds; + } + + return $this; + } + + + + /** + * Returns the most appropriate zoom to see the bounds on a map with min(width,height) = $min_w_h + * + * @param integer $min_w_h width or height of the map in pixels + * @return integer + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public function getZoom($min_w_h, $default_zoom = 14) + { + $infinity = 999999999; + $factor_h = $infinity; + $factor_w = $infinity; + + /* + + formula: the width of the bounds in "pixels" is pix_w * 2^z + We want pix_w * 2^z to fit in min_w_h so we are looking for + z = round ( log2 ( min_w_h / pix_w ) ) + */ + + $sw_lat_pix = EGMapCoord::fromLatToPix($this->getSouthWest()->getLatitude(),0); + $ne_lat_pix = EGMapCoord::fromLatToPix($this->getNorthEast()->getLatitude(),0); + $pix_h = abs($sw_lat_pix-$ne_lat_pix); + if ($pix_h > 0) + { + $factor_h = $min_w_h / $pix_h; + } + + $sw_lng_pix = EGMapCoord::fromLngToPix($this->getSouthWest()->getLongitude(),0); + $ne_lng_pix = EGMapCoord::fromLngToPix($this->getNorthEast()->getLongitude(),0); + $pix_w = abs($sw_lng_pix-$ne_lng_pix); + if ($pix_w > 0) + { + $factor_w = $min_w_h / $pix_w; + } + + $factor = min($factor_w,$factor_h); + + // bounds is one point, no zoom can be determined + if ($factor == $infinity) + { + + return $default_zoom; + } + + return round(log($factor,2)); + } + + /** + * + * Returns the boundaries that the others have + * + * @param EGMapBounds[] $boundss + * @param float $margin + * @return EGMapBounds + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public static function getBoundsContainingAllBounds($boundss, $margin = 0) + { + $min_lat = 1000; + $max_lat = -1000; + $min_lng = 1000; + $max_lng = -1000; + foreach($boundss as $bounds) + { + $min_lat = min($min_lat, $bounds->getSouthWest()->getLatitude()); + $min_lng = min($min_lng, $bounds->getSouthWest()->getLongitude()); + $max_lat = max($max_lat, $bounds->getNorthEast()->getLatitude()); + $max_lng = max($max_lng, $bounds->getNorthEast()->getLongitude()); + } + + if ($margin > 0) + { + $min_lat = $min_lat - $margin*($max_lat-$min_lat); + $min_lng = $min_lng - $margin*($max_lng-$min_lng); + $max_lat = $max_lat + $margin*($max_lat-$min_lat); + $max_lng = $max_lng + $margin*($max_lng-$min_lng); + } + + $bounds = new EGMapBounds(new EGMapCoord($min_lat, $min_lng),new EGMapCoord($max_lat, $max_lng)); + return $bounds; + } + + /** + * Retuns bounds containg an array of coordinates + * + * @param EGMapCoord[] $coords + * @param float $margin + * @return EGMapBounds + * @author fabriceb + * @since Mar 13, 2009 fabriceb + */ + public static function getBoundsContainingCoords($coords, $margin = 0) + { + $min_lat = 1000; + $max_lat = -1000; + $min_lng = 1000; + $max_lng = -1000; + foreach($coords as $coord) + { + /* @var $coord EGMapCoord */ + $min_lat = min($min_lat, $coord->getLatitude()); + $max_lat = max($max_lat, $coord->getLatitude()); + $min_lng = min($min_lng, $coord->getLongitude()); + $max_lng = max($max_lng, $coord->getLongitude()); + } + + if ($margin > 0) + { + $min_lat = $min_lat - $margin*($max_lat-$min_lat); + $min_lng = $min_lng - $margin*($max_lng-$min_lng); + $max_lat = $max_lat + $margin*($max_lat-$min_lat); + $max_lng = $max_lng + $margin*($max_lng-$min_lng); + } + $bounds = new EGMapBounds(new EGMapCoord($min_lat, $min_lng),new EGMapCoord($max_lat, $max_lng)); + + return $bounds; + } + + + /** + * + * @param GMapMarker[] $markers array of Markers + * @param float $margin margin factor for the bounds + * @return EGMapBounds + * @author fabriceb + * @since 2009-05-02 + * @since 2011-01-25 modified by Antonio Ramirez + * + **/ + public static function getBoundsContainingMarkers($markers, $margin = 0) + { + $coords = array(); + foreach($markers as $marker) + { + array_push($coords, $marker->position); + } + + return EGMapBounds::getBoundsContainingCoords($coords, $margin); + } + + + /** + * Calculate the bounds corresponding to a specific center and zoom level for a give map size in pixels + * + * @param EGMapCoord $center_coord + * @param integer $zoom + * @param integer $width + * @param integer $height + * @return EGMapBounds + * @author fabriceb + * @since Jun 2, 2009 fabriceb + */ + public static function getBoundsFromCenterAndZoom(EGMapCoord $center_coord, $zoom, $width, $height = null) + { + if (is_null($height)) + { + $height = $width; + } + + $center_lat = $center_coord->getLatitude(); + $center_lng = $center_coord->getLongitude(); + + $pix = EGMapCoord::fromLatToPix($center_lat, $zoom); + $ne_lat = EGMapCoord::fromPixToLat($pix - round(($height-1) / 2), $zoom); + $sw_lat = EGMapCoord::fromPixToLat($pix + round(($height-1) / 2), $zoom); + + $pix = EGMapCoord::fromLngToPix($center_lng, $zoom); + $sw_lng = EGMapCoord::fromPixToLng($pix - round(($width-1) / 2), $zoom); + $ne_lng = EGMapCoord::fromPixToLng($pix + round(($width-1) / 2), $zoom); + + return new EGMapBounds(new EGMapCoord($sw_lat, $sw_lng), new EGMapCoord($ne_lat, $ne_lng)); + } + + /** + * + * @param EGMapCoord $gmap_coord + * @return boolean $is_inside + * @author fabriceb + * @since Jun 2, 2009 fabriceb + */ + public function containsEGMapCoord(EGMapCoord $gmap_coord) + { + $is_inside = + ( + $gmap_coord->getLatitude() < $this->getNorthEast()->getLatitude() + && + $gmap_coord->getLatitude() > $this->getSouthWest()->getLatitude() + && + $gmap_coord->getLongitude() < $this->getNorthEast()->getLongitude() + && + $gmap_coord->getLongitude() > $this->getSouthWest()->getLongitude() + ); + + return $is_inside; + } + + +} diff --git a/extensions/EGMap/EGMapClient.php b/extensions/EGMap/EGMapClient.php new file mode 100644 index 0000000..8614e70 --- /dev/null +++ b/extensions/EGMap/EGMapClient.php @@ -0,0 +1,256 @@ + + * $gmapclient = new EGMapClient( array('domain'=>'googlekeyhere') ); + * + * @param array $key + */ + public function __construct($key = array()) + { + // $key = array( 'domain' => 'googlekeyhere' ); + $this->api_keys = new EGMapApiKeyList(); + + if (!empty($key) && !is_scalar($key)) + { + list( $domain, $key ) = each($key); + $this->setAPIKey($domain, $key); + } + } + + /** + * Sets the Google Maps API key + * @param string $key + */ + public function setAPIKey($domain, $key, $setAsDefault = false) + { + if ($this->api_keys === null) + $this->api_keys = new EGMapApiKeyList(); + + + $this->api_keys->addAPIKey($domain, $key); + + if (true === $setAsDefault) + $this->setDomain($domain); + } + + /** + * + * Sets default API key + * @param string $domain + */ + public function setDomain($domain) + { + + $this->_default_domain = $domain; + } + + /** + * Gets the Google Maps API key + * @return string $key + */ + public function getAPIKey($domain = null) + { + $domain = (null === $domain ? $this->_default_domain : $domain); + return $this->api_keys->getAPIKeyByDomain($domain); + } + + /** + * Guesses and sets default API Key + * + */ + protected function guessAndSetAPIKey($key) + { + $this->setAPIKey($this->guessDomain(), $key, true); + } + + /** + * Guesses the current domain + * @return string $domain + * @author Antonio Ramirez Cobos + * + */ + public static function guessDomain() + { + if (isset($_SERVER['SERVER_NAME'])) + return $_SERVER['SERVER_NAME']; + else if (isset($_SERVER['HTTP_HOST'])) + return $_SERVER['HTTP_HOST']; + + // nothing found, return default + return $this->_default_domain; + } + + /** + * Returns the collection of API keys + * @return CMap + */ + public function getAPIKeys() + { + return $this->api_keys; + } + + /** + * + * Sets the API keys collection + * @param CMap $api_keys + * @return false if $api_keys is not of class CMap + * @author Antonio Ramirez Cobos + */ + public function setAPIKeys($api_keys) + { + if (!$api_keys instanceof CMap) + return false; + + $this->api_keys = $api_keys; + } + + /** + * + * Changes default geocoding template + * Just in case google changes its API + * current is of default: {api}&output={format}&key={key}&q={address} + * @param string $template + * @author Antonio Ramirez Cobos + */ + public function setGeoCodingTemplate($template) + { + $this->geoCodingInfotemplate = $template; + } + + /** + * + * Connection to Google Maps' API web service + * + * Modified to include a template for api + * just in case the url changes in future releases + * Includes template parsing and CURL calls + * @author Antonio Ramirez Cobos + * @since 2010-12-21 + * + * @param string $address + * @param string $format 'csv' or 'xml' + * @return string + * @author fabriceb + * @since 2009-06-17 + * @since 2010-12-22 cUrl and Yii adaptation Antonio Ramirez + * + */ + public function getGeocodingInfo($address, $format = 'csv') + { + + $apiUrl = CChoiceFormat::format($this->geoCodingInfotemplate, array('{api}' => self::API_URL, + '{format}' => $format, + '{key}' => $this->getAPIKey(), + '{address}' => urlencode($address))); + + $apiURL = self::API_URL . '&output=' . $format . '&key=' . $this->getAPIKey() . '&q=' . urlencode($address); + + if (function_exists('curl_version')) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $apiURL); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + $raw_data = curl_exec($ch); + curl_close($ch); + } + else // no CUrl, try differently + $raw_data = file_get_contents($apiURL); + + return $raw_data; + } + + /** + * Reverse geocoding info + * + * @return string + * @author Vincent Guillon + * @since 2010-03-04 + * @since 2010-12-22 modified by Antonio Ramirez (CUrl call) + */ + public function getReverseGeocodingInfo($lat, $lng) + { + $apiURL = self::API_URL . 'll=' . $lat . ',' . $lng; + if (function_exists('curl_version')) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $apiURL); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + $raw_data = curl_exec($ch); + curl_close($ch); + } + else // no CUrl, try differently + $raw_data = file_get_contents($apiURL); + + return $raw_data; + } + +} + +?> \ No newline at end of file diff --git a/extensions/EGMap/EGMapControlPosition.php b/extensions/EGMap/EGMapControlPosition.php new file mode 100644 index 0000000..7a8b2b9 --- /dev/null +++ b/extensions/EGMap/EGMapControlPosition.php @@ -0,0 +1,42 @@ +latitude = floatval($latitude); + $this->longitude = floatval($longitude); + } + + + /** + * @return float + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public function getLatitude() + { + + return (float) $this->latitude; + } + + /** + * @return float + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public function getLongitude() + { + + return (float) $this->longitude; + } + + /** + * + * @param float $latitude + * @author fabriceb + * @since Apr 21, 2010 + */ + public function setLatitude($latitude) + { + $this->latitude = floatval($latitude); + } + + /** + * + * @param float $latitude + * @author fabriceb + * @since Apr 21, 2010 + */ + public function setLongitude($longitude) + { + $this->longitude = floatval($longitude); + } + + /** + * + * @param $string + * @return EGMapCoord + * @author fabriceb + */ + public static function createFromString($string) + { + $coord_array = explode(',',$string); + if (count($coord_array)==2) + { + $latitude = floatval(trim($coord_array[0])); + $longitude = floatval(trim($coord_array[1])); + + return new EGMapCoord($latitude,$longitude); + } + + return null; + } + + /** + * + * @return string + */ + public function toJs( ) + { + return 'new google.maps.LatLng('.$this->__toString().')'; + } + + /** + * Lng to Pix + * cf. a World's map according to Google http://mt0.google.com/mt/v=ap.92&hl=en&x=0&y=0&z=0&s= + * + * @param float $lng + * @param integer $zoom + * @return integer + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public static function fromLngToPix($lng,$zoom) + { + $lngrad = deg2rad($lng); + $mercx = $lngrad; + $cartx = $mercx + pi(); + $pixelx = $cartx * 256/(2*pi()); + $pixelx_zoom = $pixelx * pow(2,$zoom); + + return $pixelx_zoom; + } + + /** + * Lat to Pix + * cf. a World's map according to Google http://mt0.google.com/mt/v=ap.92&hl=en&x=0&y=0&z=0&s= + * + * @param float $lat + * @param integer $zoom + * @return integer + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public static function fromLatToPix($lat,$zoom) + { + if ($lat == 90) + { + $pixely = 0; + } + else if ($lat == -90) + { + $pixely = 256; + } + else + { + $latrad = deg2rad($lat); + $mercy = log(tan(pi()/4+$latrad/2)); + $carty = pi() - $mercy; + $pixely = $carty * 256 / 2 / pi(); + $pixely = max(0, $pixely); // correct rounding errors near north and south poles + $pixely = min(256, $pixely); // correct rounding errors near north and south poles + } + $pixely_zoom = $pixely * pow(2,$zoom); + + return $pixely_zoom; + } + + /** + * Pix to Lng + * cf. a World's map according to Google http://mt0.google.com/mt/v=ap.92&hl=en&x=0&y=0&z=0&s= + * + * @param integer $pix + * @param integer $zoom + * @return float + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public static function fromPixToLng($pixelx_zoom,$zoom) + { + $pixelx = $pixelx_zoom / pow(2,$zoom); + $cartx = $pixelx / 256 * 2 * pi(); + $mercx = $cartx - pi(); + $lngrad = $mercx; + $lng = rad2deg($lngrad); + + return $lng; + } + + /** + * Pix to Lat + * cf. a World's map according to Google http://mt0.google.com/mt/v=ap.92&hl=en&x=0&y=0&z=0&s= + * + * @param integer $pix + * @param integer $zoom + * @return float + * @author fabriceb + * @since Feb 18, 2009 fabriceb + */ + public static function fromPixToLat($pixely_zoom,$zoom) + { + $pixely = $pixely_zoom / pow(2,$zoom); + if ($pixely == 0) + { + $lat = 90; + } + else if ($pixely == 256) + { + $lat = -90; + } + else + { + $carty = $pixely / 256 * 2 * pi(); + $mercy = pi() - $carty; + $latrad = 2 * atan(exp($mercy))-pi()/2; + $lat = rad2deg($latrad); + } + + return $lat; + } + + /** + * Calculates the center of an array of coordiantes + * + * @param EGMapCoord[] $coords + * @return EGMapCoord + * @author fabriceb + * @since 2009-05-02 + */ + public static function getMassCenterCoord($coords) + { + if (count($coords)==0) + { + + return null; + } + $center_lat = 0; + $center_lng = 0; + foreach($coords as $coord) + { + /* @var $coord EGMapCoord */ + $center_lat += $coord->getLatitude(); + $center_lng += $coord->getLongitude(); + } + + return new EGMapCoord($center_lat/count($coords),$center_lng/count($coords)); + } + + /** + * Calculates the center of an array of coordiantes + * + * @param EGMapCoord[] $coords + * @return EGMapCoord + * @author fabriceb + * @since 2009-05-02 + */ + public static function getCenterCoord($coords) + { + $bounds = EGMapBounds::getBoundsContainingCoords($coords); + + return $bounds->getCenterCoord(); + } + + /** + * toString method + * + * @return string + * + * @author fabriceb + * @since 2009-05-02 + * @since 2010-04-21 added (float) to force . instead of , as separator. If still not working use number_format($this->getLongitude(), 10, '.', ''); + */ + public function __toString() + { + + return ((float) $this->getLatitude()).', '.((float) $this->getLongitude()); + } + + /** + * very approximate calculation of the distance in kilometers between two coordinates + * + * @param EGMapCoord $coord2 + * @return float + * + * @author fabriceb + * @since 2009-05-03 + */ + public function distanceFrom($coord2) + { + $lat_dist = abs($this->getLatitude()-$coord2->getLatitude()); + $lng_dist = abs($this->getLongitude()-$coord2->getLongitude()); + + $rad_dist = deg2rad(sqrt(pow($lat_dist,2)+pow($lng_dist,2))); + + return $rad_dist * self::EARTH_RADIUS; + } + + /** + * exact distance with spherical law of cosines + * + * @param EGMapCoord $coord2 + * @return float + * @see http://www.zipcodeworld.com/samples/distance.php.html + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public function exactDistanceSLCFrom($coord2) + { + $lat1 = $this->getLatitude(); + $lat2 = $coord2->getLatitude(); + $lon1 = $this->getLongitude(); + $lon2 = $coord2->getLongitude(); + + $theta = $lon1 - $lon2; + $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta)); + $dist = acos($dist); + $dist = rad2deg($dist); + $miles = $dist * 60 * 1.1515; + + return $miles * 1.609344; + } + + /** + * exact distance with Haversine formula + * + * @param EGMapCoord $coord2 + * @return float + * @see http://www.movable-type.co.uk/scripts/latlong.html + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public function exactDistanceFrom($coord2) + { + $lat1 = deg2rad($this->getLatitude()); + $lat2 = deg2rad($coord2->getLatitude()); + $lon1 = deg2rad($this->getLongitude()); + $lon2 = deg2rad($coord2->getLongitude()); + + $dLatHalf = ($lat2 - $lat1) / 2; + $dLonHalf = ($lon2 - $lon1) / 2; + + $a = pow(sin($dLatHalf), 2) + cos($lat1) * cos($lat2) * pow(sin($dLonHalf), 2); + $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); + + return $c * self::EARTH_RADIUS; + } + + /** + * very approximate calculation of the distance in kilometers between two coordinates + * + * @param EGMapCoord $coord1 + * @param EGMapCoord $coord2 + * @return float + * + * @author fabriceb + * @since 2009-05-03 + */ + public static function distance($coord1, $coord2) + { + + return $coord1->distanceFrom($coord2); + } + + /** + * exact distance with spherical law of cosines + * + * @param EGMapCoord $coord1 + * @param EGMapCoord $coord2 + * @return float + * @see exactDistanceSLCFrom + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public static function exactDistanceSLC($coord1, $coord2) + { + + return $coord1->exactDistanceSLCFrom($coord2); + } + + /** + * exact distance with Haversine formula + * + * @param EGMapCoord $coord1 + * @param EGMapCoord $coord2 + * @return float + * @see exactDistanceFrom + * + * @author fabriceb + * @since Apr 21, 2010 + */ + public static function exactDistance($coord1, $coord2) + { + + return $coord1->exactDistanceFrom($coord2); + } + + /** + * + * @param EGMapBounds $gmap_bounds + * @return boolean $is_inside + * + * @author fabriceb + * @since Jun 2, 2009 fabriceb + */ + public function isInsideBounds(EGMapBounds $gmap_bounds) + { + + return $gmap_bounds->containsGMapCoord($this); + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapDirection.php b/extensions/EGMap/EGMapDirection.php new file mode 100644 index 0000000..e42efeb --- /dev/null +++ b/extensions/EGMap/EGMapDirection.php @@ -0,0 +1,177 @@ + null, + // If true, instructs the Directions service to avoids toll roads where possible. Optional. + 'avoidTolls' => null, + // Location of destination. This can be specified as either a string to be geocoded or a LatLng. Required. + 'destination' => null, + // If set to true, the DirectionService will attempt to re-order the supplied intermediate waypoints to + // minimize overall cost of the route. If waypoints are optimized, inspect DirectionsRoute.waypoint_order + // in the response to determine the new ordering. + 'optimizeWaypoints' => null, + // Location of origin. This can be specified as either a string to be geocoded or a LatLng. Required. + 'origin' => null, + // Whether or not route alternatives should be provided. Optional. + 'provideRouteAlternatives' => null, + // Region code used as a bias for geocoding requests. + 'region' => null, + // Travel mode [DRIVING, WALKING, BICYCLING] + 'travelMode' => self::TRAVEL_MODE_WALKING, + // Preferred unit system to use when displaying distance. + // Defaults to the unit system used in the country of origin. + 'unitSystem' => null, + // Array of intermediate waypoints. Directions will be calculated from the origin to the destination by way of each waypoint in this array. + 'waypoints' => array(), + ); + + /** + * Construct GMapDirection object + * + * @param EGMapCoord $origin The coordinates of origin + * @param EGMapCoord $destination The coordinates of destination + * @param string $js_name The js var name + * @param array $options Array of option + * @author Vincent Guillon + * @since 2009-10-30 17:20:47 + * @since 2011-01-24 by Antonio Ramirez www.ramirezcobos.com + * New algorithms + * + */ + public function __construct(EGMapCoord $origin, EGMapCoord $destination, $js_name = 'gmap_direction', $options = array()) + { + + $this->origin = $origin; + $this->destination = $destination; + $this->setOptions(array_merge($this->options, $options)); + if ($js_name !== 'gmap_direction') + $this->setJsName($js_name); + } + + public function setRenderer(EGMapDirectionRenderer $renderer) + { + $this->renderer = $renderer; + } + + public function setOrigin(EGMapCoord $origin) + { + $this->options['origin'] = $origin; + } + + public function setDestination(EGMapCoord $destination) + { + $this->options['destination'] = $destination; + } + + /** + * Options setter + * + * @param array $options + * @author Vincent Guillon + * @since 2009-11-13 15:39:46 + * @since 2011-01-24 by Antonio Ramirez + * Modified algorithm + */ + public function setOptions($options = null) + { + if (isset($options['origin'])) + { + $this->setOrigin($options['origin']); + unset($options['origin']); + } + if (isset($options['destination'])) + { + $this->setDestination($options['destination']); + unset($options['destination']); + } + $this->options = array_merge($this->options, $options); + } + + /** + * Options getter + * + * @return array $this->options + * @author Vincent Guillon + * @since 2009-11-13 15:38:46 + */ + public function getOptions() + { + return $this->options; + } + + /** + * Generate js code for direction + * Inspired by the work of Vincent Guillon + * + * @param string $map_js_name The google map js var name + * @return $js_code The generated js to display direction + * @author Antonio Ramirez + * @since 2010-01-24 + * + */ + public function toJs($map_js_name = 'map') + { + if (null === $this->renderer) + throw new CException(Yii::t('EGMap', 'No Renderer Service has been provided')); + + $options = $this->getOptions(); + $js_name = $this->getJsName(); + + // set map to renderer + $this->renderer->map = $map_js_name; + + // Construct js code + $js_code = ''; + $js_code .= $this->renderer->toJs(); + $js_code .= 'var ' . $js_name . ' = new google.maps.DirectionsService();' . "\n"; + + // building Request + $js_code .= 'var ' . $js_name . 'Request = ' . EGMap::encode($this->options) . ';' . "\n"; + + $js_code .= $js_name . '.route(' . $js_name . 'Request, function(response, status)' . "\n"; + $js_code .= '{' . "\n"; + $js_code .= ' if (status == google.maps.DirectionsStatus.OK)' . "\n"; + $js_code .= ' {' . "\n"; + $js_code .= ' ' . $this->renderer->getJsName() . '.setDirections(response);' . "\n"; + $js_code .= ' }' . "\n"; + $js_code .= '});' . "\n"; + + return $js_code; + } + +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapDirectionRenderer.php b/extensions/EGMap/EGMapDirectionRenderer.php new file mode 100644 index 0000000..212bb60 --- /dev/null +++ b/extensions/EGMap/EGMapDirectionRenderer.php @@ -0,0 +1,123 @@ + false, + // This property indicates whether the renderer should provide + // UI to select amongst alternative routes. By default, this flag + // is false and a user-selectable list of routes will be shown in + // the directions' associated panel. To hide that list, set + // hideRouteList to true. + 'hideRouteList' => null, + // The InfoWindow in which to render text information when a marker + // is clicked. Existing info window content will be overwritten and + // its position moved. If no info window is specified, the DirectionsRenderer + // will create and use its own info window. This property will be ignored + // if suppressInfoWindows is set to true. + 'infoWindow' => null, + // Map on which to display the directions. + // Will be overriden when enabled to a EGMapDirection + 'map' => null, + // review marker options + 'markerOptions'=>null, + // The element (node getElementById().) + 'panel' => null, + // Options for the polylines. All polylines rendered by the DirectionsRenderer + // will use these options. + // Full reference on http://code.google.com/intl/en-EN/apis/maps/documentation/javascript/reference.html#PolylineOptions + 'polylineOptions' => + array( + 'clickable'=>null, + 'strokeColor'=>null, + 'strokeOpacity'=>null, + 'strokeWeight'=>null, + 'zIndex'=>null), + // By default, the input map is centered and zoomed to the bounding box of + // this set of directions. If this option is set to true, the viewport is left + //unchanged, unless the map's center and zoom were never set. + 'preserveViewPort' => null, + // The index of the route within the DirectionsResult object. The default value is 0. + 'routeIndex'=>null, + // Suppress the rendering of the BicyclingLayer when bicycling directions are requested. + 'suppressBicyclingLayer' => null, + // Suppress the rendering of the BicyclingLayer when bicycling directions are requested. + 'suppressInfoWindows' => null, + // Suppress the rendering of markers. + 'suppressMarkers' => null, + // Suppress the rendering of polylines + 'suppressPolylines' => null + ); + + /** + * Construct GMapDirectionRenderer object + * + * @param string $js_name The js var name + * @param array $options Array of options + * @author Antonio Ramirez + * @since 2011-01-24 + */ + public function __construct( $js_name = 'gmap_direction', $options = array()) + { + $this->setOptions($options); + if( $js_name !=='gmap_direction' ) + $this->setJsName($js_name); + } + + public function setOptions( $options ){ + if(isset($options['polylineOptions'])){ + $this->setPolylineOptions($options['polylineOptions']); + unset($options['polylineOptions']); + } + $this->options = array_merge( $this->options, $options ); + } + + public function setPolylineOptions( $value ) + { + if(!is_array($value)) + { + throw new CException(Yii::t('EGMap','Property "{class}.{property}" must be of type array.', + array('{class}'=>get_class($this), '{property}'=>'polylineOptions'))); + } + $this->options['polylineOptions'] = array_merge($this->options['polylineOptions'],$value); + } + + public function toJs(){ + if( null !== $this->panel ) + $this->panel = "document.getElementById('".$this->panel."')"; + + if(count($this->options['polylineOptions'])) $this->options['polylineOptions'] = CJavaScript::encode($this->options['polylineOptions']); + + return 'var '.$this->getJsName().' = new google.maps.DirectionsRenderer('.EGMap::encode($this->options).');'."\n"; + } + + +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapDirectionWayPoint.php b/extensions/EGMap/EGMapDirectionWayPoint.php new file mode 100644 index 0000000..26e45e0 --- /dev/null +++ b/extensions/EGMap/EGMapDirectionWayPoint.php @@ -0,0 +1,117 @@ + + * @since 2009-11-20 16:14:23 + * + * @copyright + * info as this library is a modified version of Fabrice Bernhard + * + * Copyright (c) 2008 Fabrice Bernhard + * 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. + * + */ +class EGMapDirectionWayPoint +{ + protected $location; + protected $stopover; + + /** + * Construct EGMapDirection object + * + * @param EGMapCoord $origin The coordinates of origin + * @param EGMapCoord $destination The coordinates of destination + * @param string $js_name The js var name + * @param array $options Array of options + * @author Vincent Guillon + * @since 2009-10-30 17:20:47 + */ + public function __construct($location = null, $stopover = true) + { + $this->setLocation($location); + $this->setStopOver($stopover); + } + + /** + * $location getter + * + * @return EGMapCoord $this->location + * @author Vincent Guillon + * @since 2009-11-20 16:16:55 + */ + public function getLocation() + { + + return $this->location; + } + + /** + * $stopover getter + * + * @return boolen $this->stopover + * @author Vincent Guillon + * @since 2009-11-20 16:17:14 + */ + public function getStopOver() + { + + return $this->stopover; + } + + /** + * $location setter + * + * @param EGMapCoord $location + * @author Vincent Guillon + * @since 2009-11-20 16:17:42 + * @since 2010-12-22 Modified for Yii Antonio Ramirez + */ + public function setLocation(EGMapCoord $location = null) + { + $this->location = $location; + } + + /** + * $stopover setter + * + * @param boolean $stopover + * @author Vincent Guillon + * @since 2009-11-20 16:19:37 + */ + public function setStopOver($stopover = true) + { + $this->stopover = $stopover; + } + + /** + * Generate javascript code fo GMapDirection waypoints option + * + * @return string + * @author Vincent Guillon + * @since 2009-11-20 16:31:42 + */ + public function toJs() + { + $stopover = $this->getStopOver() ? 'true' : 'false'; + + return '{location : '.$this->getLocation()->toJs().', stopover: '.$stopover.'}'; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapEvent.php b/extensions/EGMap/EGMapEvent.php new file mode 100644 index 0000000..f050724 --- /dev/null +++ b/extensions/EGMap/EGMapEvent.php @@ -0,0 +1,154 @@ +trigger = $trigger; + $this->function = $function; + $this->encapsulate_function = $encapsulate_function; + $this->setType($type); + } + /** + * + * Sets the type of event, by default Google Event + * @param string $type + * @throws CException + */ + public function setType( $type ){ + if( $type !== self::TYPE_EVENT_DEFAULT && $type !== self::TYPE_EVENT_DEFAULT_ONCE && + $type !== self::TYPE_EVENT_DOM && $type !== self::TYPE_EVENT_DOM_ONCE ) + throw new CException( Yii::t('EGMap', 'Unrecognized Event type') ); + $this->type = $type; + } + /** + * + * Returns type of event + * @return string + */ + public function getType( ){ + return $this->type; + } + + /** + * @return string $trigger action that will trigger the event + */ + public function getTrigger() + { + + return $this->trigger; + } + /** + * @return string $function the javascript function to be executed + */ + public function getFunction() + { + if (!$this->encapsulate_function) + return $this->function; + else + return 'function() {'.$this->function.'}'; + } + + /** + * returns the javascript code for attaching a Google event to a javascript_object + * + * @param string $js_object_name + * @return string + * @author Fabrice Bernhard + * @since 2011-07-02 Johnatan added Once support + */ + public function getEventJs($js_object_name, $once=false) + { + $once = ($once)?'Once':''; + return 'google.maps.event.addListener'.$once.'('.$js_object_name.', "'.$this->getTrigger().'", '.$this->getFunction().');'.PHP_EOL; + } + + /** + * returns the javascript code for attaching a dom event to a javascript_object + * + * @param string $js_object_name + * @return string + * @author Fabrice Bernhard + * @since 2011-07-02 Johnatan + */ + public function getDomEventJs($js_object_name, $once=false) + { + $once = ($once)?'Once':''; + return 'google.maps.event.addDomListener'.$once.'('.$js_object_name.', "'.$this->getTrigger().'", '.$this->getFunction().');'.PHP_EOL; + } + /** + * returns the javascript code for attaching a Google event or a dom event to a javascript_object + * + * @param string $js_object_name + * @return string of event type + * @author Antonio Ramirez + * @since 2011-07-02 Johnatan added Once Support + */ + public function toJs( $js_object_name ){ + switch ($this->type) { + case self::TYPE_EVENT_DEFAULT_ONCE: + return $this->getEventJs($js_object_name, true); + case self::TYPE_EVENT_DOM: + return $this->getDomEventJs($js_object_name); + case self::TYPE_EVENT_DOM_ONCE: + return $this->getDomEventJs($js_object_name, true); + case self::TYPE_EVENT_DEFAULT: + default: + return $this->getEventJs($js_object_name); + } + } + +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapGeocodeTool.php b/extensions/EGMap/EGMapGeocodeTool.php new file mode 100644 index 0000000..a4e9a0d --- /dev/null +++ b/extensions/EGMap/EGMapGeocodeTool.php @@ -0,0 +1,303 @@ +$value) + $this->{$name} = $value; + } + + public function render() + { + $this->registerScripts(); + $this->renderMap(); + + if ($this->popupOptions !== null) + { + $this->renderPopupMap(); + } + } + + protected function renderMap() + { + $gMap = new EGMap(); + $gMap->setJsName('geomap'); + $gMap->width = $this->width; + $gMap->height = $this->height; + $gMap->zoom = $this->zoom; + $gMap->setCenter($this->latitude, $this->longitude); + $gMap->addGlobalVariable('geocoder'); + $gMap->addGlobalVariable('geomap', 'null'); + if ($this->popupOptions === null) + { + $gMap->addGlobalVariable('hgeomap', 'null'); + $gMap->addGlobalVariable('hgmarker', 'null'); + } + $gMap->addEvent(new EGMapEvent('click', 'function(e){panToMap(geomap,e.latLng,hgeomap);}', false)); + $gMap->appendMapTo('#geomap'); + + $afterInit = array( + 'geocoder = new google.maps.Geocoder();', + 'gmarker = new google.maps.Marker({ + position: geomap.getCenter(), + map: geomap, + draggable: true + });', + 'google.maps.event.addListener(gmarker, "dragend", function(e) { + panToMap(geomap,e.latLng,hgeomap); + });' + ); + + echo CHtml::openTag('div', array( 'style' => 'width:' . $this->width)); + + if($this->searchBox) + { + $this->renderAddressSearchBox ('geoaddress'); + $afterInit[] = 'qDefaultText.init({"geoaddress": "Enter address here"});'; + } + if($this->geocodeOnShow) + $afterInit[] = 'panToMap(geomap,geomap.getCenter(),hgeomap)'; + + $gMap->renderMap($afterInit); + + echo '
'; + echo CHtml::openTag('ul', array('class' => 'message', 'style' => 'margin-bottom:-2px;margin-right:-2px')); + echo '
  • Drag the marker or click the map to specify new address.
  • '; + echo CHtml::closeTag('ul'); + echo '
    '; + if ($this->popupOptions !== null) + echo 'Open Big Map'; + + echo CHtml::closeTag('div'); + } + + protected function renderPopupMap() + { + $gMap = new EGMap(); + $gMap->setJsName('hgeomap'); + $gMap->addGlobalVariable('hgmarker'); + $gMap->width = isset($this->popupOptions['mapWidth']) ? $this->popupOptions['mapWidth'] : '100%'; + $gMap->height = isset($this->popupOptions['mapHeight']) ? $this->popupOptions['mapHeight'] : '600px'; + $gMap->zoom = 6; + $gMap->setCenter($this->latitude, $this->longitude); + $gMap->addEvent(new EGMapEvent('click', 'function(e){panToMap(geomap,e.latLng,hgeomap);}', false)); + $gMap->appendMapTo('#popup-map'); + $this->popupOptions['width'] = isset($this->popupOptions['width']) ? $this->popupOptions['width'] : '800px'; + + $afterInit = array( + 'hgmarker = new google.maps.Marker({ + position: hgeomap.getCenter(), + map: hgeomap, + draggable: true + });', + 'google.maps.event.addListener(hgmarker, "dragend", function(e) { + panToMap(geomap,e.latLng,hgeomap); + });', + '$("#open-big-map").click(function(e){ + e.preventDefault(); + $("#hgeomap").dialog({resizable:false,title:"Location",width:"'.$this->popupOptions['width'].'"}); + google.maps.event.trigger(hgeomap, "resize"); + hgeomap.setCenter(geomap.getCenter()); + return false; + });' + ); + + echo CHtml::openTag('div', array('id'=>'hgeomap','style' => 'display:none')); + + if(isset($this->popupOptions['searchBox']) && $this->popupOptions['searchBox']) + $this->renderAddressSearchBox ('hgeoaddress'); + if(!is_array($this->afterInitEvents)) + $this->afterInitEvents = array($this->afterInitEvents); + + $gMap->renderMap(array_merge($this->afterInitEvents,$afterInit)); + + echo ''; + echo CHtml::closeTag('div'); + } + + protected function renderAddressSearchBox($id) + { + echo '
    '; + echo CHtml::textField($id, '', array('class'=>'input-2','id'=>$id)); + echo CHtml::link('Go to','#', array('class'=>'btn-medium', 'onclick'=>'return geocode("'.$id.'");')); + echo '
    '; + echo '
    '; + } + + protected function registerScripts() + { + $updateFieldsJS=''; + $fields=array(); + if($this->latId !== null ) + $updateFieldsJS.="$('#{$this->latId}').val(ll.lat());"; + if($this->lngId !== null) + $updateFieldsJS.="$('#{$this->lngId}').val(ll.lng());"; + $updateFieldsJS.="reverseGeocode();"; + + $reverseJS=''; + if($this->addressId !== null){ + $reverseJS.="$('#{$this->addressId}').val(geoValue('route'));"; + $fields[] = '#'.$this->addressId; + } + if($this->zipId !== null){ + $reverseJS.="$('#{$this->zipId}').val(geoValue('postal_code'));"; + $fields[] = '#'.$this->zipId; + } + if($this->cityId !== null) + { + $reverseJS.="var city = geoValue('locality');"; + $reverseJS.="if(city=='') city = geoValue('sublocality');"; + $reverseJS.="$('#{$this->cityId}').val(city);"; + $fields[] = '#'.$this->cityId; + } + if($this->regionId !== null) + { + $reverseJS.="$('#{$this->regionId}').val(geoValue('administrative_area_level_1'));"; + $fields[] = '#'.$this->regionId; + } + if($this->countryId !== null) + { + $reverseJS.="var country = geoValue('country');"; + $reverseJS.="$('#{$this->countryId}').val(country);"; + $fields[] = '#'.$this->countryId; + } + if($this->formId !== null) + { + $reverseJS.=<<formId}'); +if(form.length && $.fn.yiiactiveform){ + var settings = form.data('settings'); + $.each(settings.attributes, function(){this.status = 3;}); + $.fn.yiiactiveform.validate(form,function(data){ + $.each(settings.attributes, function(i, attribute){ + $.fn.yiiactiveform.updateInput(attribute, data, form); + }); + }); +} +EOJS; + } + $fieldsStr = implode(',', $fields); + $fieldsJS = + "$('#{$this->latId}, #{$this->lngId}').change(function(){ + var latLng = new google.maps.LatLng($('#{$this->latId}').val(), $('#{$this->lngId}').val()); + panToMap(geomap,latLng,hgeomap); + }); + //var fields = ['".implode("','", $fields)."']; + //$('{$fieldsStr}').change(function(){ + // $('#{$this->latId}, #{$this->lngId}').val(''); + //}); + "; + + $js = << 0){ + if(geomap){ + geomap.fitBounds(results[0].geometry.viewport); + gmarker.setPosition(geomap.getCenter()); + updateFields(geomap.getCenter()); + } + if(hgeomap){ + hgeomap.fitBounds(results[0].geometry.viewport); + hgmarker.setPosition(geomap.getCenter()); + } + reverseGeocodeResult(results, status); + }else + qAlert("Geocode was not successful for the following reason: " + status); + } + function geoValue(type){ + var i, j, result, types, results; + results = currentReverseGeocodeResponse[0].address_components; + // Loop through the Geocoder result set. Note that the results + // array will change as this loop can self iterate. + for (i = 0; i < results.length; i++) { + result = results[i]; + types = result.types; + for (j = 0; j < types.length; j++) { + if (types[j] === type) + return result.long_name || ''; + } + } + return ''; + } +EOD; + $cs = Yii::app()->clientScript; + $cs->registerScript('EGMapGeocodeToolJS', $js, CClientScript::POS_END); + if($this->latId && $this->lngId) + $cs->registerScript('EGeocodeToolJSor',$fieldsJS, CClientScript::POS_READY); + } +} + +?> \ No newline at end of file diff --git a/extensions/EGMap/EGMapGeocodedAddress.php b/extensions/EGMap/EGMapGeocodedAddress.php new file mode 100644 index 0000000..a5942f2 --- /dev/null +++ b/extensions/EGMap/EGMapGeocodedAddress.php @@ -0,0 +1,341 @@ +raw_address = $raw_address; + } + + /** + * + * @return string $raw_address + * @author fabriceb + * @since 2009-06-17 + */ + public function getRawAddress() + { + + return $this->raw_address; + } + + /** + * Geocodes the address using the Google Maps CSV webservice + * + * @param EGMapClient $gmap_client + * @return integer $accuracy + * @author Fabrice Bernhard + */ + public function geocode($gmap_client) + { + $raw_data = $gmap_client->getGeocodingInfo($this->getRawAddress()); + $geocoded_array = explode(',', $raw_data); + if ($geocoded_array[0] != 200) + { + + return false; + } + $this->lat = $geocoded_array[2]; + $this->lng = $geocoded_array[3]; + $this->accuracy = $geocoded_array[1]; + + return $this->accuracy; + } + + /** + * Reverse geocoding + * + * @return integer + * @author Vincent Guillon + * @since 2010-03-04 + */ + public function reverseGeocode($gmap_client) + { + $raw_data = $gmap_client->getReverseGeocodingInfo($this->getLat(), $this->getLng()); + $geocoded_array = json_decode($raw_data, true); + + if ($geocoded_array['Status']['code'] != 200) + { + + return false; + } + + $this->raw_address = $geocoded_array['Placemark'][0]['address']; + $this->accuracy = $geocoded_array['Placemark'][0]['AddressDetails']['Accuracy']; + $this->geocoded_city = $geocoded_array['Placemark'][0]['AddressDetails']['Country']['AdministrativeArea']['SubAdministrativeArea']['Locality']['LocalityName']; + $this->geocoded_country_code = $geocoded_array['Placemark'][0]['AddressDetails']['Country']['CountryNameCode']; + $this->geocoded_country = $geocoded_array['Placemark'][0]['AddressDetails']['Country']['CountryName']; + $this->geocoded_address = $geocoded_array['Placemark'][0]['address']; + $this->geocoded_street = $geocoded_array['Placemark'][0]['AddressDetails']['Country']['AdministrativeArea']['SubAdministrativeArea']['Locality']['Thoroughfare']['ThoroughfareName']; + $this->geocoded_postal_code = $geocoded_array['Placemark'][0]['AddressDetails']['Country']['AdministrativeArea']['SubAdministrativeArea']['Locality']['PostalCode']['PostalCodeNumber']; + + return $this->accuracy; + } + + /** + * Geocodes the address using the Google Maps XML webservice, which has more information. + * Unknown values will be set to NULL. + * @todo Change to SimpleXML + * @param EGMapClient $gmap_client + * @return integer $accuracy + * @author Fabrice Bernhard + * @since 2010-12-22 modified by utf8_encode removed Antonio Ramirez + */ + public function geocodeXml($gmap_client) + { + $raw_data = $gmap_client->getGeocodingInfo($this->getRawAddress(), 'xml'); + + $p = xml_parser_create('UTF-8'); + xml_parse_into_struct($p, $raw_data, $vals, $index); + xml_parser_free($p); + + if ($vals[$index['CODE'][0]]['value'] != 200) + { + + return false; + } + + $coordinates = $vals[$index['COORDINATES'][0]]['value']; + list($this->lng, $this->lat) = explode(',', $coordinates); + + $this->accuracy = $vals[$index['ADDRESSDETAILS'][0]]['attributes']['ACCURACY']; + + // We voluntarily silence errors, the values will still be set to NULL if the array indexes are not defined + // @author Fabrice Bernard + @$this->geocoded_address = $vals[$index['ADDRESS'][0]]['value']; + @$this->geocoded_street = $vals[$index['THOROUGHFARENAME'][0]]['value']; + @$this->geocoded_postal_code = $vals[$index['POSTALCODENUMBER'][0]]['value']; + @$this->geocoded_country = $vals[$index['COUNTRYNAME'][0]]['value']; + @$this->geocoded_country_code = $vals[$index['COUNTRYNAMECODE'][0]]['value']; + + @$this->geocoded_city = $vals[$index['LOCALITYNAME'][0]]['value']; + if (empty($this->geocoded_city)) + { + @$this->geocoded_city = $vals[$index['SUBADMINISTRATIVEAREANAME'][0]]['value']; + } + if (empty($this->geocoded_city)) + { + @$this->geocoded_city = $vals[$index['ADMINISTRATIVEAREANAME'][0]]['value']; + } + + return $this->accuracy; + } + + /** + * Returns the latitude + * @return float $latitude + */ + public function getLat() + { + + return $this->lat; + } + + /** + * Returns the longitude + * @return float $longitude + */ + public function getLng() + { + + return $this->lng; + } + + /** + * Returns the Geocoding accuracy + * @return integer $accuracy + */ + public function getAccuracy() + { + + return $this->accuracy; + } + + /** + * Returns the address normalized by the Google Maps web service + * @return string $geocoded_address + */ + public function getGeocodedAddress() + { + + return $this->geocoded_address; + } + + /** + * Returns the city normalized by the Google Maps web service + * @return string $geocoded_city + */ + public function getGeocodedCity() + { + + return $this->geocoded_city; + } + + /** + * Returns the country code normalized by the Google Maps web service + * @return string $geocoded_country_code + */ + public function getGeocodedCountryCode() + { + + return $this->geocoded_country_code; + } + + /** + * Returns the country normalized by the Google Maps web service + * @return string $geocoded_country + */ + public function getGeocodedCountry() + { + + return $this->geocoded_country; + } + + /** + * Returns the postal code normalized by the Google Maps web service + * @return string $geocoded_postal_code + */ + public function getGeocodedPostalCode() + { + + return $this->geocoded_postal_code; + } + + /** + * Returns the street name normalized by the Google Maps web service + * @return string $geocoded_country_code + */ + public function getGeocodedStreet() + { + + return $this->geocoded_street; + } + + /** + * @param string $raw raw address to set + */ + public function setRawAddress($raw) + { + $this->raw_address = $raw; + } + + /** + * @param float $lat latitude to set + */ + public function setLat($lat) + { + $this->lat = $lat; + } + + /** + * @param float $lat longitude to set + */ + public function setLng($lng) + { + $this->lng = $lng; + } + + /** + * @param float $lat accuracy to set + */ + public function setAccuracy($accuracy) + { + $this->accuracy = $accuracy; + } + + /** + * @param string $val geocoded city + */ + public function setGeocodedCity($val) + { + $this->geocoded_city = $val; + } + + /** + * @param string $val geocoded country code + */ + public function setGeocodedCountryCode($val) + { + $this->geocoded_country_code = $val; + } + + /** + * @param string $val geocoded country code + */ + public function setGeocodedCountry($val) + { + $this->geocoded_country = $val; + } + + /** + * @param string $val geocoded address + */ + public function setGeocodedAddress($val) + { + $this->geocoded_address = $val; + } + + /** + * @param string $val geocoded street + */ + public function setGeocodedStreet($val) + { + $this->geocoded_street = $val; + } + + /** + * @param string $val geocoded postal_code + */ + public function setGeocodedPostalCode($val) + { + $this->geocoded_postal_code = $val; + } + +} diff --git a/extensions/EGMap/EGMapInfoBox.php b/extensions/EGMap/EGMapInfoBox.php new file mode 100644 index 0000000..2cbf257 --- /dev/null +++ b/extensions/EGMap/EGMapInfoBox.php @@ -0,0 +1,144 @@ + null, + // The name of the CSS class defining the styles for the InfoBox container. + // The default name is infoBox. + 'boxClass' => null, + // An object literal whose properties define specific CSS style + // values to be applied to the InfoBox. Style values defined + // here override those that may be defined in the boxClass style + // sheet. If this property is changed after the InfoBox has been + // created, all previously set styles (except those defined in + // the style sheet) are removed from the InfoBox before the new + // style values are applied. + 'boxStyle' => null, + // The CSS margin style value for the close box. The default is + // "2px" (a 2-pixel margin on all sides). + 'closeBoxMargin' => '"2px"', + // The URL of the image representing the close box. Note: The + // default is the URL for Google's standard close box. Set this + // property to "" if no close box is required. + 'closeBoxUrl'=>null, + // Propagate mousedown, click, dblclick, and contextmenu events + // in the InfoBox (default is false to mimic the behavior of a + // google.maps.InfoWindow). Set this property to true if the InfoBox + // is being used as a map label. iPhone note: This property setting + // has no effect; events are always propagated. + 'enableEventPropagation'=>null, + // Minimum offset (in pixels) from the InfoBox to the map edge + // after an auto-pan. + 'infoBoxClearance'=>null, + // Hide the InfoBox on open (default is false). + 'isHidden'=>null, + // The pane where the InfoBox is to appear (default is "floatPane"). + // Set the pane to "mapPane" if the InfoBox is being used as a map + // label. Valid pane names are the property names for the google.maps.MapPanes object. + 'pane'=>null, + ); + + /** + * @param string content + * @param array $options + * @param string $js_name + * @param array $events + * @author Maxime Picaud + * @since 7 sept. 2009 + */ + public function __construct($content, $js_name='info_box', $options = array(), $events=array()) + { + $this->options = CMap::mergeArray($this->options, $this->box_options); + + if ($js_name !== 'info_box') + $this->setJsName($js_name); + + $this->setContent($content); + + $this->setOptions($options); + $this->events = $events; + } + + /** + * @param string $map_js_name + * @return string Javascript code to create the infoBox + * @author Fabrice Bernhard + * @since 2011-10-12 by Antonio Ramirez + */ + public function toJs($map_js_name = 'map') + { + + $return = ''; + $return .= $this->getJsName() . ' = new InfoBox(' . EGMap::encode($this->options) . ');' . PHP_EOL; + + foreach ($this->custom_properties as $attribute => $value) + { + $return .= 'var ' . $this->getJsName() . "." . $attribute . " = '" . $value . "';" . PHP_EOL; + } + foreach ($this->events as $event) + { + $return .= $event->getEventJs($this->getJsName()); + } + return $return; + } + + public function getEncodedOptions() + { + return EGMap::encode(parent::getOptions()); + } +} diff --git a/extensions/EGMap/EGMapInfoWindow.php b/extensions/EGMap/EGMapInfoWindow.php new file mode 100644 index 0000000..ac705f3 --- /dev/null +++ b/extensions/EGMap/EGMapInfoWindow.php @@ -0,0 +1,257 @@ + null, + + // boolean Disable auto-pan on open. By default, the info window will pan + // the map so that it is fully visible when it opens. + 'disableAutoPan' => null, + + // number Maximum width of the infowindow, regardless of content's width. + // This value is only considered if it is set before a call to open. To change + // the maximum width when changing content, call close, setOptions, and then open. + 'maxWidth' => null, + + // Size The offset, in pixels, of the tip of the info window from the point on + // the map at whose geographical coordinates the info window is anchored. If an + // InfoWindow is opened with an anchor, the pixelOffset will be calculated from + // the top-center of the anchor's bounds. + 'pixelOffset' => null, + + // LatLng The LatLng at which to display this InfoWindow. If the InfoWindow is + // opened with an anchor, the anchor's position will be used instead. + 'position' => null, + + //number All InfoWindows are displayed on the map in order of their zIndex, with + // higher values displaying in front of InfoWindows with lower values. By default, + // InfoWinodws are displayed according to their latitude, with InfoWindows of lower + // latitudes appearing in front of InfoWindows at higher latitudes. InfoWindows are + // always displayed in front of markers. + 'zIndex' => null, + ); + + protected $events = array(); + protected $custom_properties = array(); + + /** + * @param string content + * @param array $options + * @param string $js_name + * @param array $events + * @author Maxime Picaud + * @since 7 sept. 2009 + */ + public function __construct( $content, $js_name='info_window', $options = array(), $events=array() ) + { + if( $js_name !== 'info_window' ) + $this->setJsName($js_name); + + $this->setContent($content); + + $this->setOptions($options); + $this->events = $events; + } + + /** + * + * @param string $content + * @author Maxime Picaud + * @since 7 sept. 2009 + * @since 2011-01-25 by Antonio Ramirez + * Included support to have content + * from a DOM node + */ + public function setContent( $content ) + { + if(strpos( strtolower($content), 'getelementbyid')>0) + { + $this->options['content'] = $content; + } + else + { + $content = preg_replace('/\r\n|\n|\r/', "\\n", $content); + $content = preg_replace('/(["\'])/', '\\\\\1', $content); + + $this->options['content'] = '"'.$content.'"'; + } + } + + public function getContent( ) + { + return $this->options['content']; + } + + /** + * @param array $options + * @author fabriceb + * @since 2009-08-21 + * @since 2011-01-25 by Antonio Ramirez + * Modified to check for correct values for specific options + */ + public function setOptions($options) + { + if(isset($options['pixelOffset'])) + { + if(!$options['pixelOffset'] instanceof EGMapSize) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" {e}.', + array('{class}'=>get_class($this), '{property}'=>'pixelOffset','{e}'=>'must be of type EGMapSize'))); + } + if(isset($options['position'])) + { + if(!$options['position'] instanceof EGMapCoord ) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" {e}.', + array('{class}'=>get_class($this), '{property}'=>'pixelOffset','{e}'=>'must be of type EGMapCoord'))); + } + if(isset($options['content'])){ + $this->setContent($options['content']); + unset($options['content']); + } + $this->options = array_merge($this->options,$options); + } + /** + * + * Property setter for pixelOffset + * to ensure its type + * @param EGMapSize $pixelOffset + * @author Antonio Ramirez + */ + public function setPixelOffset( EGMapSize $pixelOffset ){ + $this->options['pixelOffset'] = $pixelOffset; + } + /** + * + * Property setter position + * to ensure its type + * @param EGMapCoord $position + * @author Antonio Ramirez + */ + public function setPosition( EGMapCoord $position ){ + $this->options['position'] = $position; + } + + /** + * @param string $map_js_name + * @return string Javascript code to create the marker + * @author Fabrice Bernhard + * @since 2009-08-21 + * @since 2011-01-25 by Antonio Ramirez + * Modified options encoding + */ + public function toJs($map_js_name = 'map') + { + + $return = ''; + $return .= $this->getJsName().' = new google.maps.InfoWindow('.EGMap::encode($this->options).');'.PHP_EOL; + + foreach ($this->custom_properties as $attribute=>$value) + { + $return .= 'var '.$this->getJsName().".".$attribute." = '".$value."';".PHP_EOL; + } + foreach ($this->events as $event) + { + $return .= $event->getEventJs($this->getJsName()); + } + return $return; + } + + /** + * Adds an event listener to the marker + * + * @param EGMapEvent $event + */ + public function addEvent($event) + { + array_push($this->events,$event); + } + /** + * + * Sets custom properties to the Info Window + * @param array $custom_properties + */ + public function setCustomProperties($custom_properties) + { + if(!is_array($custom_properties)) + throw new CException(Yii::t('EGMap','EGMapInfoWindow custom properties must of type array to be set')); + $this->custom_properties=$custom_properties; + } + /** + * + * @return array custom properties + */ + public function getCustomProperties() + { + return $this->custom_properties; + } + + /** + * Sets a custom property to the generated javascript object + * + * @param string $name + * @param string $value + */ + public function setCustomProperty($name,$value) + { + $this->custom_properties[$name] = $value; + } +} diff --git a/extensions/EGMap/EGMapKMLService.php b/extensions/EGMap/EGMapKMLService.php new file mode 100644 index 0000000..c058b3d --- /dev/null +++ b/extensions/EGMap/EGMapKMLService.php @@ -0,0 +1,52 @@ +validateValue($url)) + $this->url = $url; + else + throw new CException( Yii::t('EGMap','EGMapKMLService.url must be a valid URL address') ); + } + + /** + * @return string Create new control to display latlng and coordinates under mouse. + */ + public function toJs( $map_js_name = 'map' ) + { + $return = 'var '.$this->getJsName().'= new google.maps.KmlLayer(\''.$this->url.'\');'.PHP_EOL; + $return .= $this->getJsName().'.setMap('.$map_js_name.');'.PHP_EOL; + return $return; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapKeyDragZoom.php b/extensions/EGMap/EGMapKeyDragZoom.php new file mode 100644 index 0000000..272efa0 --- /dev/null +++ b/extensions/EGMap/EGMapKeyDragZoom.php @@ -0,0 +1,177 @@ + null, + // The hot key to hold down to activate a drag zoom, shift | ctrl | alt. The default + // is shift. NOTE: Do not use Ctrl as the hot key with Google Maps JavaScript API V3 since, + // unlike with V2, it causes a context menu to appear when running on the Macintosh. Also + // note that the alt hot key refers to the Option key on a Macintosh. + 'key' => self::KEY_SHIFT, + // An object literal or named array defining the css styles of the veil pane which covers the map when a + // drag zoom is activated. The previous name for this property was paneStyle but the use of + // this name is now deprecated. The default is {backgroundColor: "gray", opacity: 0.25, cursor: "crosshair"}. + 'veilStyle' => null, + // The name of the CSS class defining the styles for the visual control. To prevent the visual control + // from being printed, set this property to the name of a class, defined inside a @media print rule, + // which sets the CSS display style to none. + 'visualClass' => null, + // A flag indicating whether a visual control is to be used. The default is false. + 'visualEnabled' => null, + // The position of the visual control. The default position is on the left side of the map below other + // controls in the top left Ñ i.e., a position of google.maps.ControlPosition.LEFT_TOP. + 'visualPosition' => EGMapControlPosition::LEFT_TOP, + // The index of the visual control. The index is for controlling the placement of the control relative + // to other controls at the position given by visualPosition; controls with a lower index are placed + // first. Use a negative value to place the control before any default controls. No index is generally + // required; the default is null. + 'visualPositionIndex'=> null, + // The width and height values provided by this property are the offsets (in pixels) from the location + // at which the control would normally be drawn to the desired drawing location. The default is (35,0). + 'visualPositionOffset'=>null, + // The width and height values provided by this property are the size (in pixels) of each of the images + // within visualSprite. The default is (20,20). + 'visualSize'=>null, + // The URL of the sprite image used for showing the visual control in the on, off, and hot (i.e., when + // the mouse is over the control) states. The three images within the sprite must be the same size and + // arranged in on-hot-off order in a single row with no spaces between images. The default is + // http://maps.gstatic.com/mapfiles/ftr/controls/dragzoom_btn.png. + 'visualSprite'=>null, + // An object literal defining the help tips that appear when the mouse moves over the visual control. + // The off property is the tip to be shown when the control is off and the on property is the tip to be + // shown when the control is on. The default values are "Turn on drag zoom mode" and "Turn off drag zoom mode", respectively. + 'visualType'=> null + ); + /** + * + * Collection of events + * @var array + */ + protected $events = array(); + + /** + * + * Class constructor + * @param array options + */ + public function __construct( $options = array() ) + { + $this->setOptions($options); + } + /** + * + * Sets plugin options + * @param array $options + * @throws CException + */ + public function setOptions( $options ){ + if(!is_array( $options )) + throw new CException( Yii::t('EGMap', 'KeyDrEGMapKeyDragZoomagZoom options must be of type array!')); + $this->options = CMap::mergeArray($this->options, $options); + + } + public function setVeilStyle( $options ){ + if(!is_array($options)) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" {e}.', + array('{class}'=>get_class($this), '{property}'=>'veilStyle','{e}'=>'must be of type array'))); + } + public function setBoxStyle( $options ){ + if(!is_array($options)) + throw new CException(Yii::t('EGMap', 'Property "{class}.{property}" {e}.', + array('{class}'=>get_class($this), '{property}'=>'boxStyle','{e}'=>'must be of type array'))); + } + public function setVisualSize( EGMapSize $size ){ + $this->options['visualSize'] = $size; + } + public function setVisualPositionOffset( EGMapSize $offset ){ + $this->options['visualPositionOffset'] = $offset; + } + + /** + * + * Adds an event to the plugin + * Note that the event must be a supported one + * @param string $trigger + * @param string $function + * @param boolean $encapsulate_function + * @throws CException + */ + public function addEvent( $trigger,$function,$encapsulate_function=true ){ + if( $trigger != self::EVENT_ACTIVATE && + $trigger != self::EVENT_DEACTIVATE && + $trigger != self::EVENT_DRAG && + $trigger != self::EVENT_DRAGEND && + $trigger != self::EVENT_DRAGSTART ) + throw new CException( Yii::t('EGMap', 'Unrecognized EGMapKeyDragZoom event!')); + + $this->events[] = new EGMapEvent( $trigger, $function, $encapsulate_function ); + + } + /** + * @return string Javascript code to return the Point + */ + public function toJs( $map_js_name = 'map' ) + { + foreach(array('veilStyle','boxStyle','visualClass','visualType') as $key) + if(isset($this->options[$key])) $this->options[$key] = CJavaScript::encode($this->options[$key]); + + $return = $map_js_name.'.enableKeyDragZoom('.EGMap::encode($this->options).');'; + + if (count($this->events)){ + $return .= 'var '.$this->getJsName().'='.$map_js_name.'.getDragZoomObject();'; + foreach($this->events as $e){ + $return .= $e->toJs($this->getJsName()); + } + } + return $return; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapLatLonControl.php b/extensions/EGMap/EGMapLatLonControl.php new file mode 100644 index 0000000..3640305 --- /dev/null +++ b/extensions/EGMap/EGMapLatLonControl.php @@ -0,0 +1,43 @@ +getJsName().'= new LatLngControl('.$map_js_name.');'.PHP_EOL; + $return .= 'google.maps.event.addListener('.$map_js_name.', "mouseover", function(e) {'.$this->getJsName().'.set("visible", true);});'.PHP_EOL; + $return .= 'google.maps.event.addListener('.$map_js_name.', "mouseout", function(e) {'.$this->getJsName().'.set("visible", true);});'.PHP_EOL; + $return .= 'google.maps.event.addListener('.$map_js_name.', "mousemove", function(e) {'.$this->getJsName().'.updatePosition(e.latLng);});'.PHP_EOL; + return $return; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapMarker.php b/extensions/EGMap/EGMapMarker.php new file mode 100644 index 0000000..3ee62b0 --- /dev/null +++ b/extensions/EGMap/EGMapMarker.php @@ -0,0 +1,405 @@ + null, + // LatLng Marker position. Required. + 'position' => null, + // string Rollover text + 'title' => null, + // Icon (string | Marker Image) for the foreground + 'icon' => null, + // Shadow image + 'shadow' => null, + // Object Image map region for drag/click. Array of x/y values that define the perimeter of the icon. + 'shape' => null, + // string Mouse cursor to show on hover + 'cursor' => null, + // boolean If true, the marker can be clicked + 'clickable' => null, + // boolean If true, the marker can be dragged. + 'draggable' => null, + // If false, disables raising and lowering the marker on drag. + // This option is true by default. + 'raiseOnDrag' => null, + // boolean If true, the marker is visible + 'visible' => null, + // boolean If true, the marker shadow will not be displayed. + 'flat' => null, + // number All Markers are displayed on the map in order of their zIndex, with higher values displaying in front of Markers with lower values. By default, Markers are displayed according to their latitude, with Markers of lower latitudes appearing in front of Markers at higher latitudes. + 'zIndex' => null, + ); + protected $info_window = null; + protected $info_window_shared = false; + protected $info_box = null; + protected $info_box_shared = false; + protected $events = null; + protected $custom_properties = array(); + + /** + * + * Included support for different types of Markers + * @var string marker object type (defaults to Marker) + */ + protected $marker_object = 'google.maps.Marker'; + + /** + * @param string $js_name Javascript name of the marker + * @param float $lat Latitude + * @param float $lng Longitude + * @param string $js_name name of the marker variable + * @param array $options of the marker + * @param EGmapEvent[] array of GoogleMap Events linked to the marker + * @author Fabrice Bernhard + * @since 2010-12-22 modified by Antonio Ramirez + */ + public function __construct($lat, $lng, $options = array(), $js_name='marker', $events=array()) + { + if ($js_name !== 'marker') + $this->setJsName($js_name); + + // position wont make any difference here + // as it will be set afterwards by setPosition + $this->setOptions($options); + + $this->setPosition(new EGMapCoord($lat, $lng)); + + $this->events = new CTypedList('EGMapEvent'); + + $this->setEvents($events); + } + + /** + * + * Batch set events by an array of EGMapEvents + * @param array $events + */ + public function setEvents($events) + { + if (!is_array($events)) + throw new CException(Yii::t('EGMap', 'Parameter of "{class}.{method}" {e}.', array('{class}' => get_class($this), '{method}' => 'setEvents', '{e}' => 'must be of type array'))); + if (null === $this->events) + $this->events = new CTypedList('EGMapEvent'); + + foreach ($events as $e) + { + $this->events->add($e); + } + } + + /** + * Adds an event listener to the marker + * + * @param EGMapEvent $event + */ + public function addEvent(EGMapEvent $event) + { + $this->events->add($event); + } + + /** + * Construct from a EGMapGeocodedAddress object + * + * @param string $js_name + * @param EGMapGeocodedAddress $gmap_geocoded_address + * @return EGMapMarker + */ + public static function constructFromGMapGeocodedAddress(EGMapGeocodedAddress $gmap_geocoded_address, $js_name='marker') + { + return new EGMapMarker($gmap_geocoded_address->getLat(), $gmap_geocoded_address->getLng(), $js_name); + } + + /** + * @param array $options + * @author fabriceb + * @since 2009-08-21 + * @modified by Antonio Ramirez + */ + public function setOptions($options) + { + if (isset($options['title'])) + $options['title'] = CJavaScript::encode($options['title']); + + $this->options = array_merge($this->options, $options); + + // double check position + if (isset($options['position'])) + { + $this->setPosition($options['position']); + } + } + + /** + * sets the coordinates object of the marker + * + * @param EGMapCoord $position + * @author Antonio Ramirez + */ + public function setPosition(EGMapCoord $position) + { + $this->options['position'] = $position; + } + + /** + * @return float $lat Javascript latitude + * @author Antonio Ramirez + */ + public function getLat() + { + return null !== $this->options['position'] ? $this->options['position']->getLatitude() : null; + } + + /** + * @return float $lng Javascript longitude + * @author Antonio Ramirez + */ + public function getLng() + { + return null !== $this->options['position'] ? $this->options['position']->getLongitude() : null; + } + + /** + * @param string $map_js_name + * @return string Javascript code to create the marker + * @author Fabrice Bernhard + * @since 2009-08-21 + * @since 2010-12-22 modified by Antonio Ramirez + * @since 2011-01-08 modified by Antonio Ramirez + * Removed EGMapMarkerImage conversion + * @since 2011-01-11 included option for global info window + * @since 2011-01-22 included different types of Marker Object support + * EGMap::encode deprecates the use of optionsToJs + * @since 2011-01-23 fixed logic bug + * @since 2011-10-12 added info_box plugin feature + * + */ + public function toJs($map_js_name = 'map') + { + $this->options['map'] = $map_js_name; + + $return = ''; + if (null !== $this->info_window || null !== $this->info_box) + { + if ($this->info_window_shared || $this->info_box_shared) + { + $info_window_name = $map_js_name . + ($this->info_window_shared? '_info_window':'_info_box'); + + $content = $this->info_window? $this->info_window->getContent() : $this->info_box->getContent(); + + $this->addEvent( + new EGMapEvent( + 'click', + // closes automatically others opened :) + //'if (' . $info_window_name . ') ' . $info_window_name . '.close();' . PHP_EOL . + $info_window_name . '.setContent(' . $content . ');' . PHP_EOL . + $info_window_name . '.open(' . $map_js_name . ',' . $this->getJsName() . ');' . PHP_EOL + )); + } else + { + $name = $this->info_window? $this->info_window->getJsName() : $this->info_box->getJsName(); + $this->addEvent(new EGMapEvent('click', $this->info_window->getJsName() . ".open(" . $map_js_name . "," . $this->getJsName() . ");" . PHP_EOL)); + $return .= $this->info_window->toJs(); + } + } + + $return .='var ' . $this->getJsName() . ' = new ' . $this->marker_object . '(' . EGMap::encode($this->options) . ');' . PHP_EOL; + + foreach ($this->custom_properties as $attribute => $value) + { + $return .= $this->getJsName() . "." . $attribute . " = '" . $value . "';" . PHP_EOL; + } + foreach ($this->events as $event) + { + $return .= $event->getEventJs($this->getJsName()) . PHP_EOL; + } + + return $return; + } + + /** + * Adds an onlick listener that open a html window with some text + * + * @param EGMapInfoWindow $info_window + * @param boolean $shared among other markers (unique info_window display) + * + * @author Antonio Ramirez + * @since 2011-01-23 Added shared functionality for infoWindows + */ + public function addHtmlInfoWindow(EGMapInfoWindow $info_window, $shared = true) + { + $this->info_window = $info_window; + $this->info_window_shared = $shared; + $this->info_box = null; + $this->info_box_shared = false; + } + /** + * + * @return boolean if info window is shared or not + */ + public function htmlInfoWindowShared() + { + return $this->info_window_shared; + } + + /** + * @return EGMapInfoWindow + * @author Antonio Ramirez + */ + public function getHtmlInfoWindow() + { + return $this->info_window; + } + + public function addHtmlInfoBox(EGMapInfoBox $info_box, $shared = true) + { + $this->info_box = $info_box; + $this->info_box_shared = $shared; + $this->info_window = null; + $this->info_window_shared = false; + } + public function htmlInfoBoxShared() + { + return $this->info_box_shared; + } + public function getHtmlInfoBox() + { + return $this->info_box; + } + + /** + * Returns the coords code for the static version of Google Maps + * @TODO Add support for color and alpha-char + * @author Laurent Bachelier + * @return string + */ + public function getMarkerStatic() + { + + return $this->getLat() . ',' . $this->getLng(); + } + + /** + * + * Sets custom properties to the Marker + * @param array $custom_properties + */ + public function setCustomProperties($custom_properties) + { + if (!is_array($custom_properties)) + throw new CException(Yii::t('EGMap', 'EGMapMarker custom properties must of type array to be set')); + $this->custom_properties = $custom_properties; + } + + /** + * + * @return array custom properties + */ + public function getCustomProperties() + { + + return $this->custom_properties; + } + + /** + * Sets a custom property to the generated javascript object + * + * @param string $name + * @param string $value + */ + public function setCustomProperty($name, $value) + { + $this->custom_properties[$name] = $value; + } + + /** + * + * @param EGMapMarker[] $markers array of Markers + * @return EGMapCoord + * @author fabriceb + * @since 2009-05-02 + * @since 2011-01-25 modified by Antonio Ramirez + * */ + public static function getMassCenterCoord($markers) + { + $coords = array(); + foreach ($markers as $marker) + { + array_push($coords, $marker->position); + } + + return EGMapCoord::getMassCenterCoord($coords); + } + + /** + * + * @param EGMapMarker[] $markers array of MArkers + * @return EGMapCoord + * @author fabriceb + * @since 2009-05-02 + * @since 2011-01-25 modified by Antonio Ramirez + * */ + public static function getCenterCoord($markers) + { + $bounds = EGMapBounds::getBoundsContainingMarkers($markers); + + return $bounds->getCenterCoord(); + } + + /** + * + * @param EGMapBounds $gmap_bounds + * @return boolean $is_inside + * @author fabriceb + * @since Jun 2, 2009 fabriceb + */ + public function isInsideBounds(EGMapBounds $gmap_bounds) + { + + return $this->getGMapCoord()->isInsideBounds($gmap_bounds); + } + +} diff --git a/extensions/EGMap/EGMapMarkerClusterer.php b/extensions/EGMap/EGMapMarkerClusterer.php new file mode 100644 index 0000000..00f20e6 --- /dev/null +++ b/extensions/EGMap/EGMapMarkerClusterer.php @@ -0,0 +1,125 @@ + null, + // The max zoom level monitored by a marker cluster. If not given, the marker cluster assumes the maximum map zoom level. + // When maxZoom is reached or exceeded all markers will be shown without cluster. + 'maxZoom' => null, + // Custom styles for the cluster markers. The array should be ordered according to increasing cluster size, with the style + // for the smallest clusters first, and the style for the largest clusters last. + // must be an array with any of the following options: + // + // height Number Image height. + // width Number Image width. + // opt_anchor Array of Number Anchor for label text, like [24, 12]. If not set, the text will align center and middle. + // opt_textColor String Text color. The default value is "black". + // url String Image url. + // + 'styles' => null, + // If you wish to setup custom images for clusterer markers. There should be + // from 1 up to 5 + // Defaults to http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/images/m + 'imagesPath' => null, + // Image extension, defaults to png + 'imagesExtension'=>null + ); + /** + * + * Collection of EGMapMarkers markers + * @var array + */ + protected $markers; + + /** + * + * Class constructor + * @param array options + */ + public function __construct( $options = array() ) + { + $this->markers = new CTypedMap('EGMapMarker'); + + $this->setOptions($options); + } + + /** + * + * Sets plugin options + * @param array $options + * @throws CException + */ + public function setOptions( $options ){ + if(!is_array( $options )) + throw new CException( Yii::t('EGMap', 'EGMapMarkerClusterer options must be of type array!')); + if(isset($options['styles'])){ + $this->setStyles($options['styles']); + unset($options['styles']); + } + $this->options = array_merge($this->options, $options); + } + /** + * + * Sets a plugin option + * @param string $name option + * @param mixed $value + */ + public function setStyles( $value ){ + $this->options['styles'] = CJavaScript::encode($value); + } + /** + * + * Adds a marker to its internal collection + * @param EGMapMarker $marker + */ + public function addMarker( EGMapMarker $marker){ + + $this->markers->add($marker->getJsName(), $marker); + } + + /** + * @return string Javascript code to return the Point + */ + public function toJs( $map_js_name = 'map' ) + { + $markers = array(); + if(count($this->markers)){ + foreach($this->markers as $m) + $markers[] = $m->getJsName(); + } + $return = 'var '.$this->getJsName().'= new MarkerClusterer('.$map_js_name.','.EGMap::encode($markers).','.EGMap::encode($this->options).');'; + + return $return; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapMarkerImage.php b/extensions/EGMap/EGMapMarkerImage.php new file mode 100644 index 0000000..2d8f4cd --- /dev/null +++ b/extensions/EGMap/EGMapMarkerImage.php @@ -0,0 +1,255 @@ + $width,'height' => $height) + * @param EGMapPoint $origin + * @param EGMapPoint $anchor + * @author Maxime Picaud + * @since 4 sept. 2009 + * @since 2011-01-21 modified by Antonio Ramirez Cobos + */ + public function __construct( $url, EGMapSize $size=null, EGMapPoint $origin=null, EGMapPoint $anchor=null) + { + $this->url = $url; + + if(null !== $size) $this->size = $size; + + if( null !== $origin) $this->origin = $origin; + + if( null !== $anchor ) $this->anchor = $anchor; + + } + + /** + * + * @return url of the image + * @author Antonio Ramirez + */ + public function getUrl() + { + return $this->url; + } + + /** + * + * @return EGMapSize | null + * @author Antonio Ramirez + */ + public function getSize() + { + return $this->size; + } + + /** + * + * @return numeric string | number size width + * @author Antonio Ramirez + */ + public function getWidth() + { + if( $this->size !== null ) + return $this->size->getWidth; + } + + /** + * + * @return numeric string |Ênumber $height + * @author Maxime Picaud + * @since 4 sept. 2009 + * @since 2011-01-22 by Antonio Ramirez + */ + public function getHeight() + { + if( $this->size !== null ) + return $this->size->getHeight; + } + + + /** + * + * @param numeric string | number $width + * @param numeric string | number $height + * @author Antonio Ramirez + * @since 2011-01-22 + */ + public function setSize($width,$height) + { + if( null === $this->size ) + $this->size = new EGMapSize(); + $this->size->setWidth($width); + $this->size->setHeight($height); + } + + /** + * + * @return EGMapPoint $anchor + * @author Maxime Picaud + * @since 4 sept. 2009 + * @since 2011-01-21 Modified by Antonio Ramirez + */ + public function getOrigin() + { + return $this->origin; + } + + /** + * + * @return numeric string | number origin CoordX + * @author Antonio Ramirez + */ + public function getOriginX() + { + if( $this->origin !== null ) + return $this->origin->getCoordX(); + + } + + /** + * + * @return numeric string | number origin CoordY + * @author Antonio Ramirez + */ + public function getOriginY() + { + if( $this->origin !== null ) + return $this->origin->getCoordY(); + } + + /** + * + * @param numeric string | number origin CoordX + * @param numeric string | number origin CoordY + * @author Antonio Ramirez + */ + public function setOrigin( $x, $y) + { + if( null === $this->origin ) + $this->origin = new EGMapPoint(); + $this->origin->setCoordX($x); + $this->origin->setCoordY($y); + } + + /** + * + * @return EGMapPoint $anchor + * @author Maxime Picaud + * @since 4 sept. 2009 + * @since 2011-01-21 Modified by Antonio Ramirez + */ + public function getAnchor() + { + return $this->anchor; + } + + /** + * + * @return numeric string | number anchor CoordX + * @author Antonio Ramirez + */ + public function getAnchorX() + { + if( $this->anchor !== null ) + return $this->anchor->getCoordX(); + } + + /** + * + * @return numeric string | number anchor CoordY + * @author Antonio Ramirez + */ + public function getAnchorY() + { + if( $this->anchor !== null ) + return $this->anchor->getCoordY(); + } + + /** + * + * @param numeric string | number anchor CoordX + * @param numeric string | number anchor CoordY + * @author Antonio Ramirez + */ + public function setAnchor($x,$y) + { + if( null === $this->anchor ) + $this->anchor = new EGMapPoint(); + $this->anchor->setCoordX($x); + $this->anchor->setCoordY($y); + } + + /** + * + * @return string js code to create the markerImage + * @author Maxime Picaud + * @since 4 sept. 2009 + * @since 2011-01-22 modified by Antonio Ramirez + * implemented EGMap support for object to + * js translation + */ + public function toJs() + { + $params = array(); + + $params[] = '"'.$this->getUrl().'"'; + $params[] = EGMap::encode($this->size); + $params[] = EGMap::encode($this->origin); + $params[] = EGMap::encode($this->anchor); + + $return = 'new google.maps.MarkerImage('.implode(',',$params).")"; + + return $return; + } + +} diff --git a/extensions/EGMap/EGMapMarkerWithLabel.php b/extensions/EGMap/EGMapMarkerWithLabel.php new file mode 100644 index 0000000..aa67b52 --- /dev/null +++ b/extensions/EGMap/EGMapMarkerWithLabel.php @@ -0,0 +1,147 @@ + null, + // The name of the CSS class defining the styles for the label. Note + // that style values for position, overflow, top, left, zIndex, display, + // marginLeft, and marginTop are ignored; these styles are for internal use only. + 'labelClass'=> null, + // The content of the label (plain text or an HTML DOM node). + 'labelContent'=> null, + // A flag indicating whether a label that overlaps its associated marker + // should appear in the background (i.e., in a plane below the marker). + // The default is false, which causes the label to appear in the foreground. + 'labelInBackGround' => null, + // An object literal whose properties define specific CSS style values to be + // applied to the label. Style values defined here override those that may be + // defined in the labelClass style sheet. If this property is changed after the + // label has been created, all previously set styles (except those defined in + // the style sheet) are removed from the label before the new style values are + // applied. Note that style values for position, overflow, top, left, zIndex, + // display, marginLeft, and marginTop are ignored; these styles are for internal use only. + 'labelStyle'=> null, + // A flag indicating whether the label is to be visible. The default is true. + // Note that even if labelVisible is true, the label will not be visible unless the + // associated marker is also visible (i.e., unless the marker's visible property is true). + 'labelVisible'=> null, + // A flag indicating whether the label and marker are to be raised when the marker is dragged. + // The default is true. If a draggable marker is being created and a version of Google + // Maps API earlier than V3.3 is being used, this property must be set to false. + 'raiseOnDrag'=>null + + ); + /** + * @param string $js_name Javascript name of the marker + * @param float $lat Latitude + * @param float $lng Longitude + * @param EGMapIcon $icon + * @param EGmapEvent[] array of GoogleMap Events linked to the marker + * @author Antonio Ramirez + */ + public function __construct( $lat, $lng, $options = array(), $js_name='marker',$events=array() ) + { + $this->marker_object = 'MarkerWithLabel'; + + $options = array_merge($this->label_options, $this->encodeOptions($options)); + + parent::__construct( $lat, $lng, $options, $js_name, $events ); + + } + /** + * + * Sets the anchor of the label + * @param EGMapPoint $anchor + */ + public function setLabelAnchor( EGMapPoint $anchor ){ + + $this->options['labelAnchor'] = $anchor; + } + /** + * + * Sets the label HTML content + * @param string $content + */ + public function setLabelContent( $content ){ + + $this->options['labelContent']='"'.$content.'"'; + } + /** + * + * Set the style class name for the label + * @param unknown_type $class + */ + public function setLabelClass( $class ){ + + $this->options['labelClass'] = '"'.$class.'"'; + + } + /** + * + * Sets label style + * position, overflow, top, left, zIndex, + * display, marginLeft, and marginTop are ignored + * @param array $styleOptions + * @throws CException + */ + public function setLabelStyle( $styleOptions ){ + if(!is_array( $styleOptions )) + throw new CException( Yii::t('EGMap', 'EGMapMarkerWithLabel label style options must be of type array!')); + $this->options['labelStyle'] = CJavaScript::encode($styleOptions); + } + /** + * (non-PHPdoc) + * @see EGMapMarker::setOptions() + */ + public function setOptions( $options ){ + parent::setOptions( $this->encodeOptions($options) ); + } + /** + * + * Encodes options appropiatelly + * @param array $options + */ + private function encodeOptions( $options ){ + if(!is_array( $options )) + throw new CException( Yii::t('EGMap', 'EGMapMarkerWithLabel.encodeOptions parameter must be of type array!')); + foreach(array('labelContent', 'labelClass', 'labelStyle') as $key ) + if(isset($options[$key])) $options[$key] = CJavaScript::encode($options[$key]); + + return $options; + } +} diff --git a/extensions/EGMap/EGMapPoint.php b/extensions/EGMap/EGMapPoint.php new file mode 100644 index 0000000..e98522c --- /dev/null +++ b/extensions/EGMap/EGMapPoint.php @@ -0,0 +1,89 @@ +setCoordX($x); + $this->setCoordY($y); + } + /** + * + * Sets X coordenate of the point + * @param integer $x + */ + public function setCoordX( $x ){ + if( !is_numeric($x) ) + throw new CException(Yii::t('EGMap','X Coordenate must be a numeric string or a number!')); + + $this->_x = $x; + } + /** + * + * Sets Y coordenate of the point + * @param integer $y + */ + public function setCoordY( $y ){ + if( !is_numeric($y) ) + throw new CException(Yii::t('EGMap','Y Coordenate must be a numeric string or a number!')); + + $this->_y = $y; + } + /** + * + * returns array representation of the coords + */ + public function toArray(){ + return array('x'=>$this->_x, 'y'=>$this->_y); + } + /** + * @return string Javascript code to return the Point + */ + public function toJs() + { + return ' new google.maps.Point('.$this->_x.','.$this->_y.')'; + } +} \ No newline at end of file diff --git a/extensions/EGMap/EGMapSize.php b/extensions/EGMap/EGMapSize.php new file mode 100644 index 0000000..4a6dd22 --- /dev/null +++ b/extensions/EGMap/EGMapSize.php @@ -0,0 +1,91 @@ +setHeight($height); + $this->setWidth($width); + } + /** + * + * Sets Height of the Size + * @param integer $height + */ + public function setHeight( $height ){ + if( !is_numeric($height) ) + throw new CException(Yii::t('EGMap','Height must be a numeric string or a number!')); + + $this->_height = $height; + } + /** + * + * Sets the Width of the Size + * @param integer $width + */ + public function setWidth( $width ){ + if( !is_numeric($width) ) + throw new CException(Yii::t('EGMap','Width must be a numeric string or a number!')); + + $this->_width = $width; + } + /** + * + * returns array representation of the size + */ + public function toArray(){ + return array('width'=>$this->_width, 'height'=>$this->_height); + } + /** + * @return string Javascript code to return the Size + */ + public function toJs() + { + return ' new google.maps.Size('.$this->_width.','.$this->_height.')'; + } +} \ No newline at end of file diff --git a/extensions/EGMap/assets/geoxml3.js b/extensions/EGMap/assets/geoxml3.js new file mode 100644 index 0000000..58b4cd1 --- /dev/null +++ b/extensions/EGMap/assets/geoxml3.js @@ -0,0 +1,1018 @@ +/* + geoxml3.js + + Renders KML on the Google Maps JavaScript API Version 3 + http://code.google.com/p/geoxml3/ + + Copyright 2010 Sterling Udell, Larry Ross + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// Extend the global String object with a method to remove leading and trailing whitespace +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, ''); + }; +} + +// Declare namespace +geoXML3 = window.geoXML3 || {instances: []}; + +// Constructor for the root KML parser object +geoXML3.parser = function (options) { + // Private variables + var parserOptions = geoXML3.combineOptions(options, { + singleInfoWindow: false, + processStyles: true, + zoom: true + }); + var docs = []; // Individual KML documents + var lastPlacemark; + var parserName; + if (!parserOptions.infoWindow && parserOptions.singleInfoWindow) + parserOptions.infoWindow = new google.maps.InfoWindow(); + // Private methods + + var parse = function (urls, docSet) { + // Process one or more KML documents + if (!parserName) { + parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']'; + } + + if (typeof urls === 'string') { + // Single KML document + urls = [urls]; + } + + // Internal values for the set of documents as a whole + var internals = { + parser: this, + docSet: docSet || [], + remaining: urls.length, + parseOnly: !(parserOptions.afterParse || parserOptions.processStyles) + }; + var thisDoc, j; + for (var i = 0; i < urls.length; i++) { + var baseUrl = urls[i].split('?')[0]; + for (j = 0; j < docs.length; j++) { + if (baseUrl === docs[j].baseUrl) { + // Reloading an existing document + thisDoc = docs[j]; + thisDoc.url = urls[i]; + thisDoc.internals = internals; + thisDoc.reload = true; + docs.splice(j, 1); + break; + } + } + thisDoc = thisDoc || { + url: urls[i], + baseUrl: baseUrl, + internals: internals + }; + internals.docSet.push(thisDoc); + geoXML3.fetchXML(thisDoc.url, function (responseXML) {render(responseXML, thisDoc);}); + } + }; + + var hideDocument = function (doc) { + if (!doc) doc = docs[0]; + // Hide the map objects associated with a document + var i; + if (!!doc.markers) { + for (i = 0; i < doc.markers.length; i++) { + if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close(); + doc.markers[i].setVisible(false); + } + } + if (!!doc.ggroundoverlays) { + for (i = 0; i < doc.ggroundoverlays.length; i++) { + doc.ggroundoverlays[i].setOpacity(0); + } + } + if (!!doc.gpolylines) { + for (i=0;i 0)) { + styles[styleID] = { + href: nodeValue(styleNodes[0].getElementsByTagName('href')[0]), + scale: nodeValue(styleNodes[0].getElementsByTagName('scale')[0]) + }; + if (!isNaN(styles[styleID].scale)) styles[styleID].scale = 1.0; + } + styleNodes = thisNode.getElementsByTagName('LineStyle'); + if (!!styleNodes && !!styleNodes.length && (styleNodes.length > 0)) { + styles[styleID].color = nodeValue(styleNodes[0].getElementsByTagName('color')[0]), + styles[styleID].width = nodeValue(styleNodes[0].getElementsByTagName('width')[0]) + } + styleNodes = thisNode.getElementsByTagName('PolyStyle'); + if (!!styleNodes && !!styleNodes.length && (styleNodes.length > 0)) { + styles[styleID].outline = getBooleanValue(styleNodes[0].getElementsByTagName('outline')[0]); + styles[styleID].fill = getBooleanValue(styleNodes[0].getElementsByTagName('fill')[0]); + styles[styleID].fillcolor = nodeValue(styleNodes[0].getElementsByTagName('color')[0]); + } + return styles[styleID]; +} + +function getBooleanValue(node) { + var nodeContents = geoXML3.nodeValue(node); + if (!nodeContents) return true; + if (nodeContents) nodeContents = parseInt(nodeContents); + if (isNaN(nodeContents)) return true; + if (nodeContents == 0) return false; + else return true; +} + +function processPlacemarkCoords(node, tag) { + var parent = node.getElementsByTagName(tag); +var coordListA = []; + for (var i=0; i 0) { + break; + } else { + return [{coordinates: []}]; + } + } + + for (var j=0; j 0)) { + var style = processStyle(node,doc.styles,"inline"); + processStyleID(style); + if (style) placemark.style = style; + } + if (/^https?:\/\//.test(placemark.description)) { + placemark.description = ['', placemark.description, ''].join(''); + } + + // process MultiGeometry + var GeometryNodes = node.getElementsByTagName('coordinates'); + var Geometry = null; + if (!!GeometryNodes && (GeometryNodes.length > 0)) { + for (var gn=0;gn= 0 ; i--) { + if (!doc.markers[i].active) { + if (!!doc.markers[i].infoWindow) { + doc.markers[i].infoWindow.close(); + } + doc.markers[i].setMap(null); + doc.markers.splice(i, 1); + } + } + } + + // Parse ground overlays + if (!!doc.reload && !!doc.groundoverlays) { + for (i = 0; i < doc.groundoverlays.length; i++) { + doc.groundoverlays[i].active = false; + } + } + + if (!!doc) { + doc.groundoverlays = doc.groundoverlays || []; + } + // doc.groundoverlays =[]; + var groundOverlay, color, transparency, overlay; + var groundNodes = responseXML.getElementsByTagName('GroundOverlay'); + for (i = 0; i < groundNodes.length; i++) { + node = groundNodes[i]; + + // Init the ground overlay object + groundOverlay = { + name: geoXML3.nodeValue(node.getElementsByTagName('name')[0]), + description: geoXML3.nodeValue(node.getElementsByTagName('description')[0]), + icon: {href: geoXML3.nodeValue(node.getElementsByTagName('href')[0])}, + latLonBox: { + north: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('north')[0])), + east: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('east')[0])), + south: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('south')[0])), + west: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('west')[0])) + } + }; + if (parserOptions.zoom && !!google.maps) { + doc.bounds = doc.bounds || new google.maps.LatLngBounds(); + doc.bounds.union(new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) + )); + } + + // Opacity is encoded in the color node + var colorNode = node.getElementsByTagName('color'); + if ( colorNode && colorNode.length && (colorNode.length > 0)) { + groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0])); + } else { + groundOverlay.opacity = 0.45; + } + + doc.groundoverlays.push(groundOverlay); + + if (!!parserOptions.createOverlay) { + // User-defined overlay handler + parserOptions.createOverlay(groundOverlay, doc); + } else { // ! user defined createOverlay + // Check to see if this overlay was created on a previous load of this document + var found = false; + if (!!doc) { + doc.groundoverlays = doc.groundoverlays || []; + if (doc.reload) { + overlayBounds = new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)); + var overlays = doc.groundoverlays; + for (i = overlays.length; i--;) { + if ((overlays[i].bounds().equals(overlayBounds)) && + (overlays.url_ === groundOverlay.icon.href)) { + found = overlays[i].active = true; + break; + } + } + } + } + + if (!found) { + // Call the built-in overlay creator + overlay = createOverlay(groundOverlay, doc); + overlay.active = true; + } + } + if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) { + var overlays = doc.groundoverlays; + for (i = overlays.length; i--;) { + if (!overlays[i].active) { + overlays[i].remove(); + overlays.splice(i, 1); + } + } + doc.groundoverlays = overlays; + } + } + // Parse network links + var networkLink; + var docPath = document.location.pathname.split('/'); + docPath = docPath.splice(0, docPath.length - 1).join('/'); + var linkNodes = responseXML.getElementsByTagName('NetworkLink'); + for (i = 0; i < linkNodes.length; i++) { + node = linkNodes[i]; + + // Init the network link object + networkLink = { + name: geoXML3.nodeValue(node.getElementsByTagName('name')[0]), + link: { + href: geoXML3.nodeValue(node.getElementsByTagName('href')[0]), + refreshMode: geoXML3.nodeValue(node.getElementsByTagName('refreshMode')[0]) + } + }; + + // Establish the specific refresh mode + if (networkLink.link.refreshMode === '') { + networkLink.link.refreshMode = 'onChange'; + } + if (networkLink.link.refreshMode === 'onInterval') { + networkLink.link.refreshInterval = parseFloat(geoXML3.nodeValue(node.getElementsByTagName('refreshInterval')[0])); + if (isNaN(networkLink.link.refreshInterval)) { + networkLink.link.refreshInterval = 0; + } + } else if (networkLink.link.refreshMode === 'onChange') { + networkLink.link.viewRefreshMode = geoXML3.nodeValue(node.getElementsByTagName('viewRefreshMode')[0]); + if (networkLink.link.viewRefreshMode === '') { + networkLink.link.viewRefreshMode = 'never'; + } + if (networkLink.link.viewRefreshMode === 'onStop') { + networkLink.link.viewRefreshTime = geoXML3.nodeValue(node.getElementsByTagName('refreshMode')[0]); + networkLink.link.viewFormat = geoXML3.nodeValue(node.getElementsByTagName('refreshMode')[0]); + if (networkLink.link.viewFormat === '') { + networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]'; + } + } + } + + if (!/^[\/|http]/.test(networkLink.link.href)) { + // Fully-qualify the HREF + networkLink.link.href = docPath + '/' + networkLink.link.href; + } + + // Apply the link + if ((networkLink.link.refreshMode === 'onInterval') && + (networkLink.link.refreshInterval > 0)) { + // Reload at regular intervals + setInterval(parserName + '.parse("' + networkLink.link.href + '")', + 1000 * networkLink.link.refreshInterval); + } else if (networkLink.link.refreshMode === 'onChange') { + if (networkLink.link.viewRefreshMode === 'never') { + // Load the link just once + doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet); + } else if (networkLink.link.viewRefreshMode === 'onStop') { + // Reload when the map view changes + + } + } + } +} + + if (!!doc.bounds) { + doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds(); + doc.internals.bounds.union(doc.bounds); + } + if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) { + doc.internals.parseOnly = false; + } + + doc.internals.remaining -= 1; + if (doc.internals.remaining === 0) { + // We're done processing this set of KML documents + // Options that get invoked after parsing completes + if (!!doc.internals.bounds) { + parserOptions.map.fitBounds(doc.internals.bounds); + } + if (parserOptions.afterParse) { + parserOptions.afterParse(doc.internals.docSet); + } + + if (!doc.internals.parseOnly) { + // geoXML3 is not being used only as a real-time parser, so keep the processed documents around + for (var i=(doc.internals.docSet.length-1);i>=0;i--) { + docs.push(doc.internals.docSet[i]); + } + } + } + }; + +var kmlColor = function (kmlIn) { + var kmlColor = {}; + if (kmlIn) { + aa = kmlIn.substr(0,2); + bb = kmlIn.substr(2,2); + gg = kmlIn.substr(4,2); + rr = kmlIn.substr(6,2); + kmlColor.color = "#" + rr + gg + bb; + kmlColor.opacity = parseInt(aa,16)/256; + } else { + // defaults + kmlColor.color = randomColor(); + kmlColor.opacity = 0.45; + } + return kmlColor; +} + +var randomColor = function(){ + var color="#"; + var colorNum = Math.random()*8388607.0; // 8388607 = Math.pow(2,23)-1 + var colorStr = colorNum.toString(16); + color += colorStr.substring(0,colorStr.indexOf('.')); + return color; +}; + + var processStyleID = function (style) { + var zeroPoint = new google.maps.Point(0,0); + if (!!style.href) { + var markerRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange|pause|go|stop)(-dot)?\.png/; + if (markerRegEx.test(style.href)) { + //bottom middle + var anchorPoint = new google.maps.Point(16*style.scale, 32*style.scale); + } else { + var anchorPoint = new google.maps.Point(16*style.scale, 12*style.scale); + } + // Init the style object with a standard KML icon + style.icon = new google.maps.MarkerImage( + style.href, + new google.maps.Size(32*style.scale, 32*style.scale), + zeroPoint, + // bottom middle + anchorPoint, + new google.maps.Size(32,32) + + ); + + // Look for a predictable shadow + var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/; + var shadowSize = new google.maps.Size(59, 32); + var shadowPoint = new google.maps.Point(16,32); + if (stdRegEx.test(style.href)) { + // A standard GMap-style marker icon + style.shadow = new google.maps.MarkerImage( + 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', + shadowSize, + zeroPoint, + shadowPoint); + } else if (style.href.indexOf('-pushpin.png') > -1) { + // Pushpin marker icon + style.shadow = new google.maps.MarkerImage( + 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', + shadowSize, + zeroPoint, + shadowPoint); + } else { + // Other MyMaps KML standard icon + style.shadow = new google.maps.MarkerImage( + style.href.replace('.png', '.shadow.png'), + shadowSize, + zeroPoint, + shadowPoint); + } + } + } + + var processStyles = function (doc) { + for (var styleID in doc.styles) { + processStyleID(doc.styles[styleID]); + } + }; + + var createMarker = function (placemark, doc) { + // create a Marker to the map from a placemark KML object + + // Load basic marker properties + var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, { + map: parserOptions.map, + position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng), + title: placemark.name, + zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5, + icon: placemark.style.icon, + shadow: placemark.style.shadow + }); + + // Create the marker on the map + var marker = new google.maps.Marker(markerOptions); + if (!!doc) { + doc.markers.push(marker); + } + + // Set up and create the infowindow + var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, { + content: '

    ' + placemark.name + + '

    ' + placemark.description + '
    ', + pixelOffset: new google.maps.Size(0, 2) + }); + if (parserOptions.infoWindow) { + marker.infoWindow = parserOptions.infoWindow; + } else { + marker.infoWindow = new google.maps.InfoWindow(infoWindowOptions); + } + // Infowindow-opening event handler + google.maps.event.addListener(marker, 'click', function() { + marker.infoWindow.setOptions(infoWindowOptions); + this.infoWindow.open(this.map, this); + }); + placemark.marker = marker; + return marker; + }; + + var createOverlay = function (groundOverlay, doc) { + // Add a ProjectedOverlay to the map from a groundOverlay KML object + + if (!window.ProjectedOverlay) { + throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML'; + } + + var bounds = new google.maps.LatLngBounds( + new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west), + new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east) + ); + var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100}); + var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions); + + if (!!doc) { + doc.ggroundoverlays = doc.ggroundoverlays || []; + doc.ggroundoverlays.push(overlay); + } + + return overlay; + }; + +// Create Polyline + + var createPolyline = function(placemark, doc) { + var path = []; + for (var j=0; j

    ' + placemark.name + + '

    ' + placemark.description + '
    ', + pixelOffset: new google.maps.Size(0, 2) + }); + var p = new google.maps.Polyline(polyOptions); + p.bounds = bounds; + if (parserOptions.infoWindow) { + p.infoWindow = parserOptions.infoWindow; + } else { + p.infoWindow = new google.maps.InfoWindow(infoWindowOptions); + } + // Infowindow-opening event handler + google.maps.event.addListener(p, 'click', function(e) { + p.infoWindow.setOptions(infoWindowOptions); + if (e && e.latLng) { + p.infoWindow.setPosition(e.latLng); + } else { + p.infoWindow.setPosition(point); + } + p.infoWindow.open(this.map); + }); + if (!!doc) doc.gpolylines.push(p); + placemark.polyline = p; + return p; +} + +// Create Polygon + +var createPolygon = function(placemark, doc) { + var bounds = new google.maps.LatLngBounds(); + var pathsLength = 0; + var paths = []; + for (var polygonPart=0;polygonPart

    ' + placemark.name + + '

    ' + placemark.description + '
    ', + pixelOffset: new google.maps.Size(0, 2) + }); + var p = new google.maps.Polygon(polyOptions); + p.bounds = bounds; + if (parserOptions.infoWindow) { + p.infoWindow = parserOptions.infoWindow; + } else { + p.infoWindow = new google.maps.InfoWindow(infoWindowOptions); + } + // Infowindow-opening event handler + google.maps.event.addListener(p, 'click', function(e) { + p.infoWindow.setOptions(infoWindowOptions); + if (e && e.latLng) { + p.infoWindow.setPosition(e.latLng); + } else { + p.infoWindow.setPosition(p.bounds.getCenter()); + } + p.infoWindow.open(this.map); + }); + if (!!doc) doc.gpolygons.push(p); + placemark.polygon = p; + return p; +} + + return { + // Expose some properties and methods + + options: parserOptions, + docs: docs, + + parse: parse, + hideDocument: hideDocument, + showDocument: showDocument, + processStyles: processStyles, + createMarker: createMarker, + createOverlay: createOverlay, + createPolyline: createPolyline, + createPolygon: createPolygon + }; +}; +// End of KML Parser + +// Helper objects and functions +geoXML3.getOpacity = function (kmlColor) { + // Extract opacity encoded in a KML color value. Returns a number between 0 and 1. + if (!!kmlColor && + (kmlColor !== '') && + (kmlColor.length == 8)) { + var transparency = parseInt(kmlColor.substr(0, 2), 16); + return transparency / 255; + } else { + return 1; + } +}; + +// Log a message to the debugging console, if one exists +geoXML3.log = function(msg) { + if (!!window.console) { + console.log(msg); + } else { alert("log:"+msg); } +}; + +// Combine two options objects: a set of default values and a set of override values +geoXML3.combineOptions = function (overrides, defaults) { + var result = {}; + if (!!overrides) { + for (var prop in overrides) { + if (overrides.hasOwnProperty(prop)) { + result[prop] = overrides[prop]; + } + } + } + if (!!defaults) { + for (prop in defaults) { + if (defaults.hasOwnProperty(prop) && (result[prop] === undefined)) { + result[prop] = defaults[prop]; + } + } + } + return result; +}; + +// Retrieve an XML document from url and pass it to callback as a DOM document +geoXML3.fetchers = []; + +// parse text to XML doc +/** + * Parses the given XML string and returns the parsed document in a + * DOM data structure. This function will return an empty DOM node if + * XML parsing is not supported in this browser. + * @param {string} str XML string. + * @return {Element|Document} DOM. + */ +geoXML3.xmlParse = function (str) { + if (typeof ActiveXObject != 'undefined' && typeof GetObject != 'undefined') { + var doc = new ActiveXObject('Microsoft.XMLDOM'); + doc.loadXML(str); + return doc; + } + + if (typeof DOMParser != 'undefined') { + return (new DOMParser()).parseFromString(str, 'text/xml'); + } + + return createElement('div', null); +} + +geoXML3.fetchXML = function (url, callback) { + function timeoutHandler() { + callback(); + }; + + var xhrFetcher; + if (!!geoXML3.fetchers.length) { + xhrFetcher = geoXML3.fetchers.pop(); + } else { + if (!!window.XMLHttpRequest) { + xhrFetcher = new window.XMLHttpRequest(); // Most browsers + } else if (!!window.ActiveXObject) { + xhrFetcher = new window.ActiveXObject('Microsoft.XMLHTTP'); // Some IE + } + } + + if (!xhrFetcher) { + geoXML3.log('Unable to create XHR object'); + callback(null); + } else { + xhrFetcher.open('GET', url, true); + xhrFetcher.onreadystatechange = function () { + if (xhrFetcher.readyState === 4) { + // Retrieval complete + if (!!geoXML3.xhrtimeout) + clearTimeout(geoXML3.xhrtimeout); + if (xhrFetcher.status >= 400) { + geoXML3.log('HTTP error ' + xhrFetcher.status + ' retrieving ' + url); + callback(); + } else { + // Returned successfully + callback(geoXML3.xmlParse(xhrFetcher.responseText)); + } + // We're done with this fetcher object + geoXML3.fetchers.push(xhrFetcher); + } + }; + geoXML3.xhrtimeout = setTimeout(timeoutHandler, 60000); + xhrFetcher.send(null); + } +}; + +//nodeValue: Extract the text value of a DOM node, with leading and trailing whitespace trimmed +geoXML3.nodeValue = function(node) { + var retStr=""; + if (!node) { + return ''; + } + if(node.nodeType==3||node.nodeType==4||node.nodeType==2){ + retStr+=node.nodeValue; + }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){ + for(var i=0;i35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('7 p(a){a=a||{};5.8.1N.2h(2,32);2.L=a.1u||"";2.1D=a.1q||H;2.P=a.1H||0;2.E=a.1B||1f 5.8.1U(0,0);2.B=a.W||1f 5.8.2t(0,0);2.S=a.11||q;2.1n=a.1l||"28";2.1k=a.D||{};2.1G=a.1E||"34";2.M=a.19||"2W://2Q.5.2L/2I/2G/2F/1v.2z";3(a.19===""){2.M=""}2.1i=a.1r||1f 5.8.1U(1,1);2.Y=a.1s||H;2.1a=a.1p||H;2.1K=a.2k||"2g";2.17=a.1m||H;2.4=q;2.w=q;2.X=q;2.16=q;2.15=q;2.13=q;2.12=q;2.O=q}p.r=1f 5.8.1N();p.r.22=7(){6 a;6 d=2;6 c=7(e){e.1Z=U;3(e.18){e.18()}};6 b=7(e){e.2S=H;3(e.1Y){e.1Y()}3(!d.17){c(e)}};3(!2.4){2.4=1g.2K("2J");2.1d();3(t 2.L.1w==="u"){2.4.J=2.F()+2.L}v{2.4.J=2.F();2.4.1b(2.L)}2.2y()[2.1K].1b(2.4);2.1F();3(2.4.9.A){2.O=U}v{3(2.P!==0&&2.4.Z>2.P){2.4.9.A=2.P;2.4.9.2u="2s";2.O=U}v{a=2.24();2.4.9.A=(2.4.Z-a.14-a.T)+"R";2.O=H}}2.1t(2.1D);3(!2.17){2.X=5.8.s.I(2.4,"2n",c);2.16=5.8.s.I(2.4,"1L",c);2.15=5.8.s.I(2.4,"2m",c);2.1o=5.8.s.I(2.4,"2l",7(e){2.9.1J="2j"})}2.12=5.8.s.I(2.4,"2i",b);5.8.s.Q(2,"2f")}};p.r.F=7(){6 a="";3(2.M!==""){a="<2e";a+=" 2d=\'"+2.M+"\'";a+=" 2c=T";a+=" 9=\'";a+=" W: 2b;";a+=" 1J: 2a;";a+=" 29: "+2.1G+";";a+="\'>"}N a};p.r.1F=7(){6 a;3(2.M!==""){a=2.4.27;2.w=5.8.s.I(a,\'1L\',2.1I())}v{2.w=q}};p.r.1I=7(){6 a=2;N 7(e){e.1Z=U;3(e.18){e.18()}a.1v();5.8.s.Q(a,"26")}};p.r.1t=7(d){6 m;6 n;6 e=0,G=0;3(!d){m=2.25();3(m 39 5.8.38){3(!m.23().37(2.B)){m.36(2.B)}n=m.23();6 a=m.35();6 h=a.Z;6 f=a.21;6 k=2.E.A;6 l=2.E.1j;6 g=2.4.Z;6 b=2.4.21;6 i=2.1i.A;6 j=2.1i.1j;6 o=2.20().31(2.B);3(o.x<(-k+i)){e=o.x+k-i}v 3((o.x+g+k+i)>h){e=o.x+g+k+i-h}3(2.1a){3(o.y<(-l+j+b)){G=o.y+l-j-b}v 3((o.y+l+j)>f){G=o.y+l+j-f}}v{3(o.y<(-l+j)){G=o.y+l-j}v 3((o.y+b+l+j)>f){G=o.y+b+l+j-f}}3(!(e===0&&G===0)){6 c=m.30();m.2Z(e,G)}}}};p.r.1d=7(){6 i,D;3(2.4){2.4.2Y=2.1n;2.4.9.2X="";D=2.1k;2V(i 2U D){3(D.2R(i)){2.4.9[i]=D[i]}}3(t 2.4.9.1h!=="u"&&2.4.9.1h!==""){2.4.9.2P="2O(1h="+(2.4.9.1h*2N)+")"}2.4.9.W="2M";2.4.9.V=\'1y\';3(2.S!==q){2.4.9.11=2.S}}};p.r.24=7(){6 c;6 a={1e:0,1c:0,14:0,T:0};6 b=2.4;3(1g.1x&&1g.1x.1V){c=b.2H.1x.1V(b,"");3(c){a.1e=C(c.1T,10)||0;a.1c=C(c.1S,10)||0;a.14=C(c.1R,10)||0;a.T=C(c.1W,10)||0}}v 3(1g.2E.K){3(b.K){a.1e=C(b.K.1T,10)||0;a.1c=C(b.K.1S,10)||0;a.14=C(b.K.1R,10)||0;a.T=C(b.K.1W,10)||0}}N a};p.r.2D=7(){3(2.4){2.4.2C.2B(2.4);2.4=q}};p.r.1A=7(){2.22();6 a=2.20().2A(2.B);2.4.9.14=(a.x+2.E.A)+"R";3(2.1a){2.4.9.1c=-(a.y+2.E.1j)+"R"}v{2.4.9.1e=(a.y+2.E.1j)+"R"}3(2.Y){2.4.9.V=\'1y\'}v{2.4.9.V="1X"}};p.r.2T=7(a){3(t a.1l!=="u"){2.1n=a.1l;2.1d()}3(t a.D!=="u"){2.1k=a.D;2.1d()}3(t a.1u!=="u"){2.1Q(a.1u)}3(t a.1q!=="u"){2.1D=a.1q}3(t a.1H!=="u"){2.P=a.1H}3(t a.1B!=="u"){2.E=a.1B}3(t a.1p!=="u"){2.1a=a.1p}3(t a.W!=="u"){2.1z(a.W)}3(t a.11!=="u"){2.1P(a.11)}3(t a.1E!=="u"){2.1G=a.1E}3(t a.19!=="u"){2.M=a.19}3(t a.1r!=="u"){2.1i=a.1r}3(t a.1s!=="u"){2.Y=a.1s}3(t a.1m!=="u"){2.17=a.1m}3(2.4){2.1A()}};p.r.1Q=7(a){2.L=a;3(2.4){3(2.w){5.8.s.z(2.w);2.w=q}3(!2.O){2.4.9.A=""}3(t a.1w==="u"){2.4.J=2.F()+a}v{2.4.J=2.F();2.4.1b(a)}3(!2.O){2.4.9.A=2.4.Z+"R";3(t a.1w==="u"){2.4.J=2.F()+a}v{2.4.J=2.F();2.4.1b(a)}}2.1F()}5.8.s.Q(2,"2x")};p.r.1z=7(a){2.B=a;3(2.4){2.1A()}5.8.s.Q(2,"1O")};p.r.1P=7(a){2.S=a;3(2.4){2.4.9.11=a}5.8.s.Q(2,"2w")};p.r.2v=7(){N 2.L};p.r.1C=7(){N 2.B};p.r.33=7(){N 2.S};p.r.2r=7(){2.Y=H;3(2.4){2.4.9.V="1X"}};p.r.2q=7(){2.Y=U;3(2.4){2.4.9.V="1y"}};p.r.2p=7(c,b){6 a=2;3(b){2.B=b.1C();2.13=5.8.s.2o(b,"1O",7(){a.1z(2.1C())})}2.1M(c);3(2.4){2.1t()}};p.r.1v=7(){3(2.w){5.8.s.z(2.w);2.w=q}3(2.X){5.8.s.z(2.X);5.8.s.z(2.16);5.8.s.z(2.15);5.8.s.z(2.1o);2.X=q;2.16=q;2.15=q;2.1o=q}3(2.13){5.8.s.z(2.13);2.13=q}3(2.12){5.8.s.z(2.12);2.12=q}2.1M(q)};',62,196,'||this|if|div_|google|var|function|maps|style||||||||||||||||InfoBox|null|prototype|event|typeof|undefined|else|closeListener_|||removeListener|width|position_|parseInt|boxStyle|pixelOffset_|getCloseBoxImg_|yOffset|false|addDomListener|innerHTML|currentStyle|content_|closeBoxURL_|return|fixedWidthSet_|maxWidth_|trigger|px|zIndex_|right|true|visibility|position|eventListener1_|isHidden_|offsetWidth||zIndex|contextListener_|moveListener_|left|eventListener3_|eventListener2_|enableEventPropagation_|stopPropagation|closeBoxURL|alignBottom_|appendChild|bottom|setBoxStyle_|top|new|document|opacity|infoBoxClearance_|height|boxStyle_|boxClass|enableEventPropagation|boxClass_|eventListener4_|alignBottom|disableAutoPan|infoBoxClearance|isHidden|panBox_|content|close|nodeType|defaultView|hidden|setPosition|draw|pixelOffset|getPosition|disableAutoPan_|closeBoxMargin|addClickHandler_|closeBoxMargin_|maxWidth|getCloseClickHandler_|cursor|pane_|click|setMap|OverlayView|position_changed|setZIndex|setContent|borderLeftWidth|borderBottomWidth|borderTopWidth|Size|getComputedStyle|borderRightWidth|visible|preventDefault|cancelBubble|getProjection|offsetHeight|createInfoBoxDiv_|getBounds|getBoxWidths_|getMap|closeclick|firstChild|infoBox|margin|pointer|relative|align|src|img|domready|floatPane|apply|contextmenu|default|pane|mouseover|dblclick|mousedown|addListener|open|hide|show|auto|LatLng|overflow|getContent|zindex_changed|content_changed|getPanes|gif|fromLatLngToDivPixel|removeChild|parentNode|onRemove|documentElement|mapfiles|en_us|ownerDocument|intl|div|createElement|com|absolute|100|alpha|filter|www|hasOwnProperty|returnValue|setOptions|in|for|http|cssText|className|panBy|getCenter|fromLatLngToContainerPixel|arguments|getZIndex|2px|getDiv|setCenter|contains|Map|instanceof'.split('|'),0,{})) \ No newline at end of file diff --git a/extensions/EGMap/assets/keydragzoom_packed.js b/extensions/EGMap/assets/keydragzoom_packed.js new file mode 100644 index 0000000..e5c184c --- /dev/null +++ b/extensions/EGMap/assets/keydragzoom_packed.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(B(){9 u=B(a){9 b;2t(a){1t"4h":b="3V";1w;1t"3z":b="2r";1w;1t"3f":b="37";1w;2Y:b=a}N b};9 t=B(h){9 b;9 a={};A(H.1F&&H.1F.1A){b=h.3s.1F.1A(h,"");A(b){a.F=O(b.2o,10)||0;a.1m=O(b.2h,10)||0;a.C=O(b.2Q,10)||0;a.1n=O(b.2M,10)||0;N a}}1f A(H.1x.1y){A(h.1y){a.F=O(u(h.1y.2o),10)||0;a.1m=O(u(h.1y.2h),10)||0;a.C=O(u(h.1y.2Q),10)||0;a.1n=O(u(h.1y.2M),10)||0;N a}}a.F=O(h.7["1I-F-G"],10)||0;a.1m=O(h.7["1I-1m-G"],10)||0;a.C=O(h.7["1I-C-G"],10)||0;a.1n=O(h.7["1I-1n-G"],10)||0;N a};9 v={x:0,y:0};9 s=B(e){v.x=(1i H.1x.1P!=="1r"?H.1x.1P:H.24.1P);v.y=(1i H.1x.1N!=="1r"?H.1x.1N:H.24.1N)};s();9 q=B(e){9 a=0,1L=0;e=e||1R.J;A(1i e.2n!=="1r"){a=e.2n;1L=e.36}1f A(1i e.2F!=="1r"){a=e.2F+v.x;1L=e.31+v.y}N{C:a,F:1L}};9 n=B(h){9 e=h.2k;9 g=h.2g;9 b=h.2K;4f(b!==S){A(b!==H.24&&b!==H.1x){e-=b.1P;g-=b.1N}9 m=b;9 f=m.2k;9 a=m.2g;A(!f&&!a&&1R.1A){9 d=H.1F.1A(m,S).40||H.1F.1A(m,S).3Z;A(d){A(1i d==="3X"){9 c=d.3S(",");f+=O(c[4],10)||0;a+=O(c[5],10)||0}}}e+=f;g+=a;b=b.2K}N{C:e,F:g}};9 o=B(a,b){A(a&&b){1b(9 x 3L b){A(b.3J(x)){a[x]=b[x]}}}N a};9 r=B(h,a){A(1i a!=="1r"){h.7.1u=a}A(1i h.7.1u!=="1r"&&h.7.1u!==""){h.7.3I="3E(1u="+(h.7.1u*3C)+")"}};B R(a,d){9 b=6;9 c=15 E.D.3w();c.3u=B(){b.2v(a,d)};c.3q=B(){};c.3p=B(){};c.2u(a);6.1C=c}R.P.2v=B(a,c){9 i;9 b=6;6.L=a;c=c||{};6.14=c.3j||"1M";6.14=6.14.3h();6.T=t(6.L.13());6.8=[];1b(i=0;i<4;i++){6.8[i]=H.1O("22");6.8[i].3a=B(){N X};o(6.8[i].7,{27:"34",1u:0.25,2A:"32"});o(6.8[i].7,c.30);o(6.8[i].7,c.2Z);o(6.8[i].7,{1K:"21",2j:"2i",W:"1c"});A(6.14==="1M"){6.8[i].7.2W="1c"}r(6.8[i]);A(6.8[i].7.27==="2V"){6.8[i].7.27="2T";r(6.8[i],0)}6.L.13().2f(6.8[i])}6.1z=c.4c||X;6.2P=c.49||"";6.1Y=c.45||E.D.44.42;6.1X=c.41||15 E.D.2L(35,0);6.2e=c.3Y||S;6.2J=c.3W||"3U://D.3T.3R/3Q/3P/1W/3O.3N";6.Y=c.3M||15 E.D.2L(20,20);6.12=c.3K||{};6.12.1l=6.12.1l||"2H 1G 29 2E 2D";6.12.1G=6.12.1G||"2H 1l 29 2E 2D";6.I=H.1O("22");o(6.I.7,{1I:"2r 3H #3G"});o(6.I.7,c.3D);o(6.I.7,{1K:"21",W:"1c"});r(6.I);6.L.13().2f(6.I);6.1d=t(6.I);6.28=[E.D.J.19(H,"3B",B(e){b.2z(e)}),E.D.J.19(H,"3A",B(e){b.1U(e)}),E.D.J.19(6.8[0],"1E",B(e){b.1B(e)}),E.D.J.19(6.8[1],"1E",B(e){b.1B(e)}),E.D.J.19(6.8[2],"1E",B(e){b.1B(e)}),E.D.J.19(6.8[3],"1E",B(e){b.1B(e)}),E.D.J.19(H,"1E",B(e){b.2y(e)}),E.D.J.19(H,"3v",B(e){b.1T(e)}),E.D.J.19(H,"3t",B(e){b.2x(e)}),E.D.J.19(1R,"3r",s)];6.Z=X;6.1Q=X;6.1h=X;6.V=S;6.11=S;6.1p=S;6.1a=S;6.26=S;6.1j=S;A(6.1z){6.K=6.2s(6.1X);A(6.2e!==S){6.K.3o=6.2e}6.L.1W[6.1Y].3n(6.K);6.2q=6.L.1W[6.1Y].1e-1}};R.P.2s=B(a){9 b;9 c;9 d=6;b=H.1O("22");b.3l=6.2P;b.7.1K="3k";b.7.2j="2i";b.7.M=6.Y.M+"w";b.7.G=6.Y.G+"w";b.1q=6.12.1l;c=H.1O("3i");c.3g=6.2J;c.7.1K="21";c.7.C=-(6.Y.G*2)+"w";c.7.F=0+"w";b.2f(c);b.3m=B(e){d.Z=!d.Z;A(d.Z){d.K.1v.7.C=-(d.Y.G*0)+"w";d.K.1q=d.12.1G;d.23=1g;E.D.J.1k(d,"2p")}1f{d.K.1v.7.C=-(d.Y.G*2)+"w";d.K.1q=d.12.1l;E.D.J.1k(d,"2w")}d.1T(e)};b.3e=B(){d.K.1v.7.C=-(d.Y.G*1)+"w"};b.3d=B(){A(d.Z){d.K.1v.7.C=-(d.Y.G*0)+"w";d.K.1q=d.12.1G}1f{d.K.1v.7.C=-(d.Y.G*2)+"w";d.K.1q=d.12.1l}};b.3c=B(){N X};o(b.7,{2A:"3b",3x:a.M+"w",3y:a.G+"w"});N b};R.P.1S=B(e){9 a;e=e||1R.J;a=(e.39&&6.14==="1M")||(e.38&&6.14==="2G")||(e.33&&6.14==="2m");A(!a){2t(e.3F){1t 16:A(6.14==="1M"){a=1g}1w;1t 17:A(6.14==="2m"){a=1g}1w;1t 18:A(6.14==="2G"){a=1g}1w}}N a};R.P.2C=B(){9 c=6.26;A(c){9 b=6.1j;9 a=6.L.13();N c.C>b.C&&c.C<(b.C+a.2B)&&c.F>b.F&&c.F<(b.F+a.2l)}1f{N X}};R.P.2d=B(){9 i;A(6.L&&6.Z&&6.2C()){9 d=6.L.13();6.1p=d.2B-(6.T.C+6.T.1n);6.1a=d.2l-(6.T.F+6.T.1m);A(6.23){9 a=O(6.K.7.C,10)+6.1X.G;9 b=O(6.K.7.F,10)+6.1X.M;9 c=6.Y.G;9 e=6.Y.M;6.8[0].7.F="U";6.8[0].7.C="U";6.8[0].7.G=a+"w";6.8[0].7.M=6.1a+"w";6.8[1].7.F="U";6.8[1].7.C=(a+c)+"w";6.8[1].7.G=(6.1p-(a+c))+"w";6.8[1].7.M=6.1a+"w";6.8[2].7.F="U";6.8[2].7.C=a+"w";6.8[2].7.G=c+"w";6.8[2].7.M=b+"w";6.8[3].7.F=(b+e)+"w";6.8[3].7.C=a+"w";6.8[3].7.G=c+"w";6.8[3].7.M=(6.1a-(b+e))+"w";1b(i=0;i<6.8.1e;i++){6.8[i].7.W="2a"}}1f{6.8[0].7.C="U";6.8[0].7.F="U";6.8[0].7.G=6.1p+"w";6.8[0].7.M=6.1a+"w";1b(i=1;i<6.8.1e;i++){6.8[i].7.G="U";6.8[i].7.M="U"}1b(i=0;i<6.8.1e;i++){6.8[i].7.W="2a"}}}1f{1b(i=0;i<6.8.1e;i++){6.8[i].7.W="1c"}}};R.P.2z=B(e){A(6.L&&!6.Z&&6.1S(e)){6.1j=n(6.L.13());6.Z=1g;6.23=X;6.2d();E.D.J.1k(6,"2p")}A(6.1z&&6.1S(e)){6.K.7.W="1c"}};R.P.1H=B(e){9 a=q(e);9 p=15 E.D.1D();p.x=a.C-6.1j.C-6.T.C;p.y=a.F-6.1j.F-6.T.F;p.x=Q.1s(p.x,6.1p);p.y=Q.1s(p.y,6.1a);p.x=Q.1V(p.x,0);p.y=Q.1V(p.y,0);N p};R.P.1B=B(e){A(6.L&&6.Z){6.1j=n(6.L.13());6.1h=1g;6.V=6.11=6.1H(e);6.I.7.G=6.I.7.M="U";9 a=6.1C.2b();9 b=a.2c(6.V);A(6.1z){6.K.7.W="1c"}E.D.J.1k(6,"2X",b)}};R.P.2y=B(e){6.1Q=1g};R.P.1T=B(e){6.26=q(e);A(6.1h){6.11=6.1H(e);9 c=Q.1s(6.V.x,6.11.x);9 b=Q.1s(6.V.y,6.11.y);9 f=Q.1o(6.V.x-6.11.x);9 g=Q.1o(6.V.y-6.11.y);9 d=Q.1V(0,f-(6.1d.C+6.1d.1n));9 a=Q.1V(0,g-(6.1d.F+6.1d.1m));6.8[0].7.F="U";6.8[0].7.C="U";6.8[0].7.G=c+"w";6.8[0].7.M=6.1a+"w";6.8[1].7.F="U";6.8[1].7.C=(c+f)+"w";6.8[1].7.G=(6.1p-(c+f))+"w";6.8[1].7.M=6.1a+"w";6.8[2].7.F="U";6.8[2].7.C=c+"w";6.8[2].7.G=f+"w";6.8[2].7.M=b+"w";6.8[3].7.F=(b+g)+"w";6.8[3].7.C=c+"w";6.8[3].7.G=f+"w";6.8[3].7.M=(6.1a-(b+g))+"w";6.I.7.F=b+"w";6.I.7.C=c+"w";6.I.7.G=d+"w";6.I.7.M=a+"w";6.I.7.W="2a";E.D.J.1k(6,"29",15 E.D.1D(c,b+g),15 E.D.1D(c+f,b),6.1C.2b())}1f A(!6.1Q){6.1j=n(6.L.13());6.2d()}};R.P.2x=B(e){9 z;9 g=6;6.1Q=X;A(6.1h){A((6.1H(e).x===6.V.x)&&(6.1H(e).y===6.V.y)){6.1U(e);N}9 k=Q.1s(6.V.x,6.11.x);9 f=Q.1s(6.V.y,6.11.y);9 l=Q.1o(6.V.x-6.11.x);9 b=Q.1o(6.V.y-6.11.y);9 c=1g;A(c){k+=6.T.C;f+=6.T.F}9 m=6.1C.2b();9 d=m.2c(15 E.D.1D(k,f+b));9 j=m.2c(15 E.D.1D(k+l,f));9 h=15 E.D.2U(d,j);z=6.L.2I();6.L.2S(h);A(6.L.2I()', + point.x, + 'px, ', + point.y, + 'px' + ].join(''); +}; \ No newline at end of file diff --git a/extensions/EGMap/assets/markerclusterer_packed.js b/extensions/EGMap/assets/markerclusterer_packed.js new file mode 100644 index 0000000..9ef8385 --- /dev/null +++ b/extensions/EGMap/assets/markerclusterer_packed.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(5(){7 d=32,f=33,g=34;5 j(a){8 5(b){3[a]=b}}5 k(a){8 5(){8 3[a]}}7 l;5 m(a,b,c){3.1f(m,13.12.22);3.b=a;3.a=[];3.m=[];3.$=[31,2X,2Y,35,3c];3.i=[];3.z=g;c=c||{};3.f=c.3b||37;3.V=c.1A||f;3.i=c.39||[];3.U=c.2V||3.O;3.T=c.2K||3.N;3.M=d;6(c.23!=25)3.M=c.23;3.p=g;6(c.21!=25)3.p=c.21;n(3);3.18(a);3.I=3.b.1t();7 e=3;13.12.1m.1x(3.b,"2M",5(){7 h=e.b.1P[e.b.1R()].1A,o=e.b.1t();6(!(o<0||o>h))6(e.I!=o){e.I=e.b.1t();e.k()}});13.12.1m.1x(3.b,"2U",5(){e.h()});b&&b.14&&3.B(b,g)}l=m.4;l.O="3E://13-12-3C-3B-3G.3M.3H/3I/3x/3l/3m/m";l.N="3k";l.1f=5(a,b){8 5(c){15(7 e 3g c.4)3.4[e]=c.4[e];8 3}.2D(a,[b])};l.1j=5(){6(!3.z){3.z=d;q(3)}};l.1k=5(){};5 n(a){6(!a.i.14)15(7 b=0,c;c=a.$[b];b++)a.i.16({1B:a.U+(b+1)+"."+a.T,1c:c,1l:c})}l.w=k("i");l.q=k("a");l.S=5(){8 3.a.14};l.H=5(){8 3.V||3.b.1P[3.b.1R()].1A};l.F=5(a,b){15(7 c=0,e=a.14,h=e;h!==0;){h=1y(h/10,10);c++}c=9.24(c,b);8{1e:e,1C:c}};l.Y=j("F");l.G=k("F");l.B=5(a,b){15(7 c=0,e;e=a[c];c++)t(3,e);b||3.h()};5 t(a,b){b.1g(g);b.18(f);b.r=g;b.2s&&13.12.1m.1x(b,"2t",5(){b.r=g;a.k();a.h()});a.a.16(b)}l.o=5(a,b){t(3,a);b||3.h()};5 u(a,b){7 c=-1;6(a.a.1n)c=a.a.1n(b);17 15(7 e=0,h;h=a.a[e];e++)6(h==b){c=e;27}6(c==-1)8 g;a.a.2z(c,1);b.1g(g);b.18(f);8 d}l.W=5(a,b){7 c=u(3,a);6(!b&&c){3.k();3.h();8 d}17 8 g};l.X=5(a,b){15(7 c=g,e=0,h;h=a[e];e++){h=u(3,h);c=c||h}6(!b&&c){3.k();3.h();8 d}};l.R=5(){8 3.m.14};l.1d=k("b");l.18=j("b");l.v=k("f");l.Z=j("f");l.u=5(a){7 b=3.1X(),c=1a 13.12.1F(a.1I().19(),a.1I().1i()),e=1a 13.12.1F(a.1J().19(),a.1J().1i());c=b.1v(c);c.x+=3.f;c.y-=3.f;e=b.1v(e);e.x-=3.f;e.y+=3.f;c=b.1Z(c);b=b.1Z(e);a.1f(c);a.1f(b);8 a};l.P=5(){3.k();3.a=[]};l.k=5(){15(7 a=0,b;b=3.m[a];a++)b.1p();15(a=0;b=3.a[a];a++){b.r=g;b.18(f);b.1g(g)}3.m=[]};l.h=5(){q(3)};5 q(a){6(a.z)15(7 b=a.u(1a 13.12.1z(a.b.1r().1J(),a.b.1r().1I())),c=0,e;e=a.a[c];c++)6(!e.r&&b.26(e.1b())){7 h=a;e=e;15(7 o=3h,r=f,x=0,p=2c 0;p=h.m[x];x++){7 i=p.1u();6(i){i=i;7 s=e.1b();6(!i||!s)i=0;17{7 y=(s.19()-i.19())*9.1q/1o,z=(s.1i()-i.1i())*9.1q/1o;i=9.1s(y/2)*9.1s(y/2)+9.2b(i.19()*9.1q/1o)*9.2b(s.19()*9.1q/1o)*9.1s(z/2)*9.1s(z/2);i=2h*2*9.2g(9.2a(i),9.2a(1-i))}6(i3.j.H())15(a=0;b=3.a[a];a++){b.18(3.b);b.1g(d)}17 6(3.a.14<2)B(3.l);17{b=3.j.G()(3.a,3.j.w().14);3.l.20(3.d);a=3.l;a.A=b;a.2i=b.1e;a.2e=b.1C;6(a.c)a.c.1Y=b.1e;b=9.2j(0,a.A.1C-1);b=9.24(a.i.14-1,b);b=a.i[b];a.L=b.1B;a.g=b.1c;a.n=b.1l;a.J=b.2d;a.e=b.2x;a.K=b.2k;a.C=b.2v;3.l.1W()}8 d};l.1r=5(){15(7 a=1a 13.12.1z(3.d,3.d),b=3.q(),c=0,e;e=b[c];c++)a.1f(e.1b());8 a};l.1p=5(){3.l.1p();3.a.14=0;2y 3.a};l.Q=5(){8 3.a.14};l.q=k("a");l.1u=k("d");5 A(a){a.D=a.j.u(1a 13.12.1z(a.d,a.d))}l.1d=k("b");5 w(a,b,c){a.j.1f(w,13.12.22);3.i=b;3.2B=c||0;3.t=a;3.d=f;3.b=a.1d();3.A=3.c=f;3.s=g;3.18(3.b)}l=w.4;l.1j=5(){3.c=1Q.2A("2u");6(3.s){3.c.1h.1M=C(3,D(3,3.d));3.c.1Y=3.A.1e}3.2n().2m.2l(3.c);7 a=3;13.12.1m.2o(3.c,"2p",5(){7 b=a.t.j;13.12.1m.2q(b,"2w",a.t);b.M&&a.b.2r(a.t.1r())})};5 D(a,b){7 c=a.1X().1v(b);c.x-=1y(a.n/2,10);c.y-=1y(a.g/2,10);8 c}l.1k=5(){6(3.s){7 a=D(3,3.d);3.c.1h.1D=a.y+"E";3.c.1h.1G=a.x+"E"}};5 B(a){6(a.c)a.c.1h.1N="2C";a.s=g}l.1W=5(){6(3.c){3.c.1h.1M=C(3,D(3,3.d));3.c.1h.1N=""}3.s=d};l.1p=5(){3.18(f)};l.1E=5(){6(3.c&&3.c.1O){B(3);3.c.1O.2f(3.c);3.c=f}};l.20=j("d");5 C(a,b){7 c=[];6(1Q.3r)c.16(\'3q:3p:3s.3t.3v(3u=3o,3n="\'+a.L+\'");\');17{c.16("1S-3i:1B("+a.L+");");c.16("1S-28:"+(a.C?a.C:"0 0")+";")}6(1H a.e==="3j"){1H a.e[0]==="1V"&&a.e[0]>0&&a.e[0]0&&a.e[1]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 literal 0 HcmV?d00001 diff --git a/extensions/EGMap/assets/markers/m2.png b/extensions/EGMap/assets/markers/m2.png new file mode 100644 index 0000000000000000000000000000000000000000..b999cbcf69441f194768157d79a113b0fae03e3c GIT binary patch literal 3259 zcmV;s3`FyZP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXJ| z2q`s%E;NS#01P`xL_t(&-o=_*Y^7&)hM)KM_F)`*4xUNE_0hLw-_KzSa zVYuKVxBwCf3AG4Ss^F#)>P1gdE2Q>!-(Glqdp_^j9((Lbf63a`KK%c;zV)tmt@W-?T!O1=fP`!muQfmteL&4- zS)#J3Bsw;hu#Jmyf2w7w)5lOMYu^nFc?Pk1AP*G_W7?!5QeY3vGQ3*q75tq z%Qmf|q`ecHt?hUz+wEbS%Ya(CZy1t{TK73Nr0<5&25hqo%oZiB?RZq%@ScDnU=L8N zWlXmCrK~UBG2FgK5XJg)9DV0ZRe9woR3s z4;1088qjAmATa>+r_IQ`nG9XDnJr3|-CWs_grIF3*kjz7%}^FoibYAH1I*ei6eUy* z`hoqxUWt(ui{Y^fn3b5cnJP-Es(~a%WAl{Vp9}%>Maj&Lc`zAQHKqIa)e3N$qno~BXB*uXSdj%y~TsvFyqQtc7R2PD*HbW#;gF#?l zK+CbX0%%E0*%T5t*t`nZ8~3fW34A2)XU4W|CV}5eoDA6RjpsRAb6a9UbvilWZP|n2 zxhpfTIY?!uD4BJW>uqj{l%O86=`JY6X7|Av`DEm>+IF)j@l#;hX1~;p`Sjh&X1plz zMR_o}-%WM{BN<6d22|f5anPm{p3Nb*XtN+OVKXCfCLos#>x8%WNQ?oyfL)RNbITr~vpHLooZD6m_8J=3L7O`gv=UiJ%mK$# zXWC8v#OA%g?hLkh^6!PHfZy4CvnYAeO|G#y90ZdXIuT6+SV%;5p;V!*RxVY8TO4B`8?1CQ(EhXh*DF8I0Km+F8ykfMtoNZN3T& zOT0M>hDTLrd?VJ}cBz(m^7RtG1&-L7gcZbHM#AK&`ia zwV49`Av`xyl$?4+YSCn*n_Lh4WmZQPVc?Sz57?Xn?g;4q0k9jm8Tg6B0`Q2<4<()g zZU_F@=7#XHqa&FC{-P-H#T4AzDYjFxKWcL{*mNHFGVl`cd5ODiUY*6-syrC)ElK*%XSw}>{Z-m%SK-%aW^jdg0uiwf~r%b}v=#KlS|0E*^uA21}*iol)& z28)uV3*Dee?vy-n_ga3Hc4f9+b(dM)rj2oy!hT-D&MRf7)j zlEf+1nai5q+|bVgySEy1n}qG%Hn*wHvGoj#_^$;qodL!|=qiB4WLV#-(rZO;8H+M( zpUqWOgX0o!w|T^7+)dii!rYy!WIO4^DBnH<+?%DQ8qlXYOI3rLfvY7(Bi($@rZ0X6 zFi=Y^N%U5dSO%_0ZA-+}1aPg*yMd$8Hu^0iU=HK;f>LCtXgO9dWy}^b=t9v zzNh16gZ8DjF2bN4{Uw=n()+!aiCx7y2GO(dcI3vCgs+R=&hQFdA&>NDZ!NDC8sUuY zRm;n*jBFNq=z5pUe1d2|<^b^grG{v1^s}MK)?%~!fM-K@C98dx*NVaRs>)`CRhKde zo=~RGWOUrIc?0l3c>7WTHPQY3zztdFF&QapPvVOxHJ9wwVN(Q_ongHn7!Q>!$q!?p zyCkKn9bNd-5|fvLMT4y+@ucd^0XHQ@&St`9G166H)(V*Koop3xqjq-Vn(8dXR3~Yi zBfxm%m;}+A!r%ulNq$gwp?_U9xCyu>^XLl_wU}Wf)vXmoRAj1MtB`PfkW$1z7&;#D zmTJ_&P?`I(vDTfyzs79hQo!_S;C|p{{ z3`*?~ijD$zNj#irdXrU-vjU?6z8H$!M}ap+SEEP)|KcVGZLW?Gk;HU#H+wlh-#4&S zlq=0`a_LN#3vIK@X2#~l#7tq#5uj=F3yHyiPAlZT_u0G)_=L@5CJlO{R1h)!Gl^2- zuYgb6yfemUL~(zAR8wz?J~eI537bBNfz)(oUC;TlM)eh4GgStW2S=_l%h?&RktV!+^<@xzVt*0%jfO(sd>_+=T zmpNf`V|ce^bF0m$#8+%ei4R3PQ%Nk_+$?bm@GaH(AK-u2P{D2l{yd)7g;hYVWAinM zM{WKn^{5Hbz_AF8EAoR15@*+JmsIZxNwZl3b?nfX>Kq47r}=|K74pIDz;nM z2}fd}YOuQ~`B5lW3Eq}C0Ng1tC-HgUzhhQd#ow>i(=dyYP9*4|I63i@&3{PzO~mj! z0^Z5^0yqi$61Y4I8O7vxHgnY4QbRU58jh0{&J?!=;H=G?fa~I1On$)NY_w3PVu9Bd zC68<*9#;+CBC*Tn35nULn63{ptFllL@N?jKV1LAJ9;;`Hl2)%ystXJ{_k_5Uj1+KA zbtc{9CY#r-HAIQ;KPT~o>MNUYf%*Sl;QBa<(t8LY9T|BHc){j!iCR`<6Z86oHI?lP z3|1tuFHS&I*{r=4d2Wx*>*Mf1)iWuoNql1?1GCM%E84V9f_~J&RUA?{9!4I>DwwpE z8P%CvZ%9*Yw7W%#Lo%Gt6}UKCc10xq9|I3Z67J7fdE8z(q_c_P&5J#oGXR{CcqsDZ zP;6q69bD)GrlVI}cLKGpfyvbpsLt77S0~jim~>R)fX$TZG;JQTIU70Zv~=OoS3rLv zEBhuSzE_ky3@idy2Gf$CxUiWhO6IfEA3JggVL0T&B=zTqf0lsxnCT4xouXuUllR>f zaG#b~w7E=TIFRCOc9@_=WWsY|Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXJ| z2r4YfXwQTI01os?L_t(|+QppRkDT{a$3N$p**Dv}w&S-XPE*@SlQd3B1ag};2>KdS z3R0`Kf(8}!h8x;)!zEG$5*PdhhzklW6{ScNDMCV2qzDpHq7qaJPU0kXV!KJ~#36p$ z*O{H?aB;rBea<`{$GhuYdn9XiXJ?-0_dCCHzUOpUNOgc;$jOTr?a^X%GF|X4NdJap#A~2|uv$fZ8(>%hV156SpKOs+WZTyP; zmlwRXm{ITd!Wnvc^p@M2^E6wi|3Ir=7h$o&2K1tc4qw@Y(LLgYcrs6h7qw z3&5Pi1%pclwTv?fZ1s5d>~U!ywZmKmm)D9LwpuT+VKBUf0KwwE)(VGt;F3^&o5OyC zy&lG^lDlJ9$Blvo7l1c_GxDeoFk>*4LDhy2V$s5ozFLFW8oglL;xxMj#PVqr56~ud z0e2J6c$4;48^&dmW?}r=;4R>U!+9BeTA9h(I0E!gsI0~qw%U-OWrWsgEFpJvxD~j| zV24PNd$Yi71hZD+@D@~LJfPtv9pG)?6@xcL#-H+O z5~xb2_K0%5B^NKJaU6I_?*3teUjlZCes_fav@oI=bD6-G8hG8|-+&*hSi8^RVDh|t z&pC%5iHdu*h7`JHLX`}1zeQH?#2rlF^sMObqYj^z2YHKZdGey1o--EYQ@;#6Zt$cB z5dS=|tr#R>)-i+Q^=1;P5ux%R;j(4XCxDj>4mmt#a6mPIw@y4W&OypR-4?AT5iaux z#TOmEYjD=#FmOk@)^USl>nXt2U27x|eSnDf7d-O(CxI_0WesR*j7qDW6^LC1=7`wt zS%lhc4pRc<8D)YxojMkwwrub{ho@!iL%^#QytLjbbR){}iU80FgI@ zEidR2IsOTU9}^V&QHKu!cMu!grj5R#!tAU^mU-IY34!K^9X=tiS@uTdS_J;g;OI>k zW)fbEJABsQ<1$9q;ip7A&j4R{c+}va2Y3!uOe6G(Sqsd09Qaci^~(fj>8bqw2ZJA4 zW1O4&@Zq0-vxdK{;q!GECgz%35(?jh^8iMcc1!=mC3dbjsn58uoeA1Kafa zmU={LuCH=&heI{IQg4C*T840kf0y=g&4a2(@4NC)+qc#*R%sIxdVhzeAAtphe%RrD zhkdnr6jjRriA#J9xO20kR`)&P@I|gdqVnd&zPfdzmQAYw+#xc!rEvQ$!+E@?5A4x);nBzx*dp5B>If}>qq6)t=RU;EV z;BcqGQy#?Y?o{&krJVO$7aQx^a7U@s(#tM4N~~|uqYl`wHeSa(J;ZI|;EThqYCtrmP;z+DylWZ@groAl+6hti{ag z{RgcnuQ&Z+P4DEeYLLFgzhy$vy4}Kq2dz*YC*Ct8=vGMsET`{Pgu%|1r}Wi8naqyj zaq}?`&GKf@`}-vEEhhEawWivKH*9JWUD0=~u9jo%%B-_2w4Ep?q89x-@~Y}4*a4k* zN17$cQ>O``xo*`$KFVd+vUs;htDVFfYQ@|5h!DGx#y}#^s5b4W!3%Gr`C4VYIfX)b zp(Ay9K>&5Ls$VzQVJ!g6&FE41@~$3{$dR?MV@wKC&xgBFr3rJx^#E&h(Ar^NR>M6) z_F_t%D?Qaw*Q%_=5Jf#reT96}aW!k^8CIjW3N&BzCO&V77wAU+rKVH2MPLSM6n9(U zeL1V=*(3U?G2=9YUM-1%31S%Jw1`Q$=@g+ppX3In&$1bV7u3+n#o}dypJ~0F)`FJQ zYw23kidbpU*D0o)NImC#ILl1R@~@`pH}u*ej|R*;egmX}g#5307~=hw$^`Xd-}DfM z$C9U(Hf1+8hRkAcrKv44>8K(%_q<_ol`MbS8(=J+a+sjnM{R?&(AAJBhbIhP)%`n( z+ssbP12vzx5?jgPIf)~ zB7vG&h(ezMo&mn%F-THd(^`DiVx;Jvmg^hvd>z==E%;Uj-;5A#R|Y^!JFOT;Ma!HA zcjzRdvR}Hy_vEIfEbmF2`F-G@fiF0`-k!W$s{!q*82QWPOJRwb<_{Zx;r)4DBUeA}X_{6HxKw2z*_F`B{E%!=ogT~muCo68WmSs?MVz$Xnpr_@{G)+CIaa(G(O*&Yj__aTFaTr(7UbQJ$TC1U-8-uJN7 z@ea+xI|QzM-(o!Ah{L|LwyqB0S*6!)3{Tt50+-S_RFgLJ;-vUOl?=2j`n;DAOUGOG zJ6XZe8a`$rv3LB+A@ZJ>>ho1JamT!I)yxx}UDF2tOJoA~Cch5|wEhS&=T^Wv;6;JwTf(RV z7K5w3RDe|yaZeF*T3b|k7BQxz?Mqd(AFkNAt)f%DUB;+~g)A;9e;*>cNn6SWb)(WM zI8UJWS-Isk=b|<4JR^P7)T7u#;8RsHO=?%pIlNrKNso!VX@+s7Qvc=R)rL^*T0J!_ zYNsaP>Xe6FIsBq@io*(xfen9or4&8`i>cTPt7Mjw9!1p@ac@g1T3JI^zdaY67Tqzu+jV^q=&wveX% zX@=4J;w7gf74AvwHhaPx@l7DF-{s`HtLd~F3Is=s!*~_}V;yzJg2DW1reo{j`G(shv7Hk@OsbtX-s)zRHL;4CM|OH zc&jd1=`u;}UeM|E*K;rJbV73tlSNNp*!Tq(dKLv>Z>>Wb>r$K3iB-TB4Uc!z6uvXe z?H7o6_q7_Mr0>KDh9><}M}AHsi`VPuXLi?Ph?I+K<>w`9zL(iai!PM6w;n-PVdbqj zFSOhA1(#XLjLT~Yg$*%8%NQ8+Ykf6Yt$=Mxt3}xDuhmsk#w!0yK$XI-UoeRDzXx2; zywbW2LDut|QyA1?jCmPitNygS);LU?>nqc^hC$}a5PH?>|6fm6>Hh&6nXpifC1hg& O0000KLZ*U+=)p!fv7f#TG` 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 literal 0 HcmV?d00001 diff --git a/extensions/EGMap/assets/markers/m5.png b/extensions/EGMap/assets/markers/m5.png new file mode 100644 index 0000000000000000000000000000000000000000..61387d2ab5c8d22efef4846617567a3b1ae6d72e GIT binary patch literal 6839 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 literal 0 HcmV?d00001 diff --git a/extensions/EGMap/assets/markerwithlabel_packed.js b/extensions/EGMap/assets/markerwithlabel_packed.js new file mode 100644 index 0000000..c8e2493 --- /dev/null +++ b/extensions/EGMap/assets/markerwithlabel_packed.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('8 t(a){2.3=a;2.6=X.1v("1V");2.6.4.L="R: 1g; 15: 1A;";2.p=X.1v("1V");2.p.4.L=2.6.4.L;2.p.23("2L","1Q w;");2.p.23("2w","1Q w;");2.v=X.1v("2o");2.v.4.L="R: 1g; z-2l: 2g; I: 16;";2.v.4.1b="-2a";2.v.4.1w="-2Y";2.v.2V="22://5.1X.1T/1R/1P/1M/2x.2v"}t.s=W 7.5.2n();t.s.2m=8(){r g=2;r l=w;r c=w;r o;r f;r i,12;r n;r d;r m=20;r h="29(22://5.1X.1T/1R/1P/1M/28.27)";r j=8(e){9(e.24){e.24()}e.2R=G;9(e.1Z){e.1Z()}};r k=8(){g.3.1W(2J)};2.1n().1S.S(2.6);2.1n().2D.S(2.p);2.1n().1S.S(2.v);2.1p=[7.5.q.M(2.p,"1N",8(e){9(g.3.N()||g.3.U()){2.4.19="1Y";7.5.q.B(g.3,"1N",e)}}),7.5.q.M(2.p,"1U",8(e){9((g.3.N()||g.3.U())&&!c){2.4.19=g.3.2r();7.5.q.B(g.3,"1U",e)}}),7.5.q.M(2.p,"1J",8(e){i=0;12=0;c=w;9(g.3.N()){l=G;2.4.19=h}9(g.3.N()||g.3.U()){7.5.q.B(g.3,"1J",e)}j(e)}),7.5.q.M(X,"1G",8(a){r b;9(l){l=w;g.p.4.19="1Y";7.5.q.B(g.3,"1G",a)}9(c){a.E=o;n=G;9(d){b=g.Q().1i(g.3.11());b.y+=m;g.3.J(g.Q().1E(b));2k{g.3.1W(7.5.2j.2i);2h(k,2f)}2e(e){}g.v.4.I="16"}g.3.T(f);c=w;7.5.q.B(g.3,"1D",a)}}),7.5.q.u(g.3.2d(),"2c",8(a){r b;9(l){a.E=W 7.5.2b(a.E.1d()-i,a.E.1c()-12);9(c){o=a.E;b=g.Q().1i(a.E);9(d){g.v.4.Y=b.x+"A";g.v.4.P=b.y+"A";g.v.4.I="";b.y-=m}g.3.J(g.Q().1E(b));9(d){g.p.4.P=(b.y+m)+"A"}7.5.q.B(g.3,"1C",a)}V{i=a.E.1d()-g.3.11().1d();12=a.E.1c()-g.3.11().1c();f=g.3.1a();g.3.T(1B);d=g.3.D("14");c=G;7.5.q.B(g.3,"1z",a)}}}),7.5.q.M(2.p,"1y",8(e){9(g.3.N()||g.3.U()){9(n){n=w}V{7.5.q.B(g.3,"1y",e);j(e)}}}),7.5.q.M(2.p,"1x",8(e){9(g.3.N()||g.3.U()){7.5.q.B(g.3,"1x",e);j(e)}}),7.5.q.u(2.3,"1z",8(a){9(!c){d=2.D("14")}}),7.5.q.u(2.3,"1C",8(a){9(!c){9(d){g.J(m);g.6.4.K=1B+(2.D("18")?-1:+1)}}}),7.5.q.u(2.3,"1D",8(a){9(!c){9(d){g.J(0)}}}),7.5.q.u(2.3,"2X",8(){g.J()}),7.5.q.u(2.3,"2W",8(){g.T()}),7.5.q.u(2.3,"2U",8(){g.17()}),7.5.q.u(2.3,"2T",8(){g.17()}),7.5.q.u(2.3,"2S",8(){g.1t()}),7.5.q.u(2.3,"2Q",8(){g.1f()}),7.5.q.u(2.3,"2P",8(){g.1e()}),7.5.q.u(2.3,"2O",8(){g.Z()}),7.5.q.u(2.3,"2M",8(){g.Z()})]};t.s.2K=8(){r i;2.6.1r.1h(2.6);2.p.1r.1h(2.p);2.v.1r.1h(2.v);26(i=0;i<2.1p.2I;i++){7.5.q.2G(2.1p[i])}};t.s.2F=8(){2.1f();2.1t();2.Z()};t.s.1f=8(){r a=2.3.D("1j");9(F a.2E==="H"){2.6.13=a;2.p.13=2.6.13}V{2.6.13="";2.6.S(a);a=a.2C(G);2.p.S(a)}};t.s.1t=8(){2.p.2B=2.3.2A()||""};t.s.Z=8(){r i,C;2.6.1o=2.3.D("1m");2.p.1o=2.6.1o;2.6.4.L="";2.p.4.L="";C=2.3.D("C");26(i 2z C){9(C.2y(i)){2.6.4[i]=C[i];2.p.4[i]=C[i]}}2.1L()};t.s.1L=8(){2.6.4.R="1g";2.6.4.15="1A";9(F 2.6.4.O!=="H"&&2.6.4.O!==""){2.6.4.1K="1O(O="+(2.6.4.O*2u)+")"}2.p.4.R=2.6.4.R;2.p.4.15=2.6.4.15;2.p.4.O=0.2H;2.p.4.1K="1O(O=1)";2.1e();2.J();2.17()};t.s.1e=8(){r a=2.3.D("1q");2.6.4.1b=-a.x+"A";2.6.4.1w=-a.y+"A";2.p.4.1b=-a.x+"A";2.p.4.1w=-a.y+"A"};t.s.J=8(a){r b=2.Q().1i(2.3.11());9(F a==="H"){a=0}2.6.4.Y=b.x+"A";2.6.4.P=(b.y-a)+"A";2.p.4.Y=2.6.4.Y;2.p.4.P=2.6.4.P;2.T()};t.s.T=8(){r a=(2.3.D("18")?-1:+1);9(F 2.3.1a()==="H"){2.6.4.K=2t(2.6.4.P,10)+a;2.p.4.K=2.6.4.K}V{2.6.4.K=2.3.1a()+a;2.p.4.K=2.6.4.K}};t.s.17=8(){9(2.3.D("1l")){2.6.4.I=2.3.2s()?"2N":"16"}V{2.6.4.I="16"}2.p.4.I=2.6.4.I};8 1k(a){a=a||{};a.1j=a.1j||"";a.1q=a.1q||W 7.5.2q(0,0);a.1m=a.1m||"2p";a.C=a.C||{};a.18=a.18||w;9(F a.1l==="H"){a.1l=G}9(F a.14==="H"){a.14=G}9(F a.21==="H"){a.21=G}9(F a.1I==="H"){a.1I=w}2.1H=W t(2);7.5.1s.25(2,1F)}1k.s=W 7.5.1s();1k.s.1u=8(a){7.5.1s.s.1u.25(2,1F);2.1H.1u(a)};',62,185,'||this|marker_|style|maps|labelDiv_|google|function|if||||||||||||||||eventDiv_|event|var|prototype|MarkerLabel_|addListener|crossDiv_|false||||px|trigger|labelStyle|get|latLng|typeof|true|undefined|display|setPosition|zIndex|cssText|addDomListener|getDraggable|opacity|top|getProjection|position|appendChild|setZIndex|getClickable|else|new|document|left|setStyles||getPosition|cLngOffset|innerHTML|raiseOnDrag|overflow|none|setVisible|labelInBackground|cursor|getZIndex|marginLeft|lng|lat|setAnchor|setContent|absolute|removeChild|fromLatLngToDivPixel|labelContent|MarkerWithLabel|labelVisible|labelClass|getPanes|className|listeners_|labelAnchor|parentNode|Marker|setTitle|setMap|createElement|marginTop|dblclick|click|dragstart|hidden|1000000|drag|dragend|fromDivPixelToLatLng|arguments|mouseup|label|draggable|mousedown|filter|setMandatoryStyles|mapfiles|mouseover|alpha|en_us|return|intl|overlayImage|com|mouseout|div|setAnimation|gstatic|pointer|stopPropagation||clickable|http|setAttribute|preventDefault|apply|for|cur|closedhand_8_8|url|8px|LatLng|mousemove|getMap|catch|1406|1000002|setTimeout|BOUNCE|Animation|try|index|onAdd|OverlayView|img|markerLabels|Point|getCursor|getVisible|parseInt|100|png|ondragstart|drag_cross_67_16|hasOwnProperty|in|getTitle|title|cloneNode|overlayMouseTarget|nodeType|draw|removeListener|01|length|null|onRemove|onselectstart|labelstyle_changed|block|labelclass_changed|labelanchor_changed|labelcontent_changed|cancelBubble|title_changed|labelvisible_changed|visible_changed|src|zindex_changed|position_changed|9px'.split('|'),0,{})) \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLFeed.php b/extensions/EGMap/kml/EGMapKMLFeed.php new file mode 100644 index 0000000..f787225 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLFeed.php @@ -0,0 +1,394 @@ + + * + * + * + * ----------------------------------------------- + * Example + * ------------------------------------------------ + + + J. K. Rowling + + + + + + ------------------------------------------------ + * + * + * + * + * + * + * ------------------------------------------------ + * Example + * ------------------------------------------------ + + #myIconStyleID + + + + and we have a link http://www.google.com. + ]]> + + + -90.86948943473118,48.25450093195546 + + + * ------------------------------------------------ + * + ------------------------------------------------ + + ------------------------------------------------ + + + * ------------------------------------------------ + * + * + * + * + * + * ------------------------------------------------ + * Example + * ------------------------------------------------ + + + 48.25475939255556 + 48.25207367852141 + -90.86591508839973 + -90.8714285289695 + + + ------------------------------------------------ + * + * + * + * + * + * ------------------------------------------------ + * Example + * ------------------------------------------------ + + + NE US Radar + + http://www.example.com/geotiff/NE/MergedReflectivityQComposite.kml + onInterval // headers HTTP only compatible with mode "onExpire" + 30 + onStop + 7 + + + + * ------------------------------------------------ + * + * + * + * + * + * + * ------------------------------------------------ + * Example + * ------------------------------------------------ + + + // only supported here + + + -122.366212,37.818977,30 + -122.365424,37.819294,30 + + + + + + + unextruded + + + -122.364383,37.824664,0 -122.364152,37.824322,0 + + + + ------------------------------------------------ + * Other supported tags by Google Maps v3 + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +class EGMapKMLFeed { + /** + * + * Holds all tags on the feed + * @var unknown_type + */ + protected $elements; + + const STYLE_ICON = 'Icon'; // when using addStyle Array it specifies is an IconStyle + const STYLE_LINE = 'Line'; // when using addStyle Array it specifies is an LineStyle + const STYLE_POLY = 'Poly'; // when using addStyle Array it specifies is an PolyStyle + /** + * + * constructor + */ + function __construct(){ + $this->elements = new CMap(); + } + /** + * + * ATOM style author + * @param string $name + */ + public function setAuthor( $name ){ + if( null === $this->elements->itemAt('head') ) + $this->elements->add('head', new CMap() ); + $item = ''.$name.''; + + $this->elements->itemAt('head')->add('author', $item); + + } + /** + * + * ATOM style link + * @param string $url + */ + public function setLink( $url ){ + if( null === $this->elements->itemAt('head') ) + $this->elements->add('head', new CMap() ); + $validator = new CUrlValidator(); + if(!$validator->validateValue($url)) + throw new CException( Yii::t('EGMap', 'EGMapKMLFeed.setLink Url does not seem to valid') ); + $item = ''; + $this->elements->itemAt('head')->add('link', $item); + } + /** + * + * Adding an externally created Tag at the end of its 'body' section + * Note: this method does not validates the tag + * @param EGMapKMLNode $tag + * @return string id of the inserted Tag + */ + public function addTag( EGMapKMLNode $tag ){ + if( null === $this->elements->itemAt('body') ) + $this->elements->add('body', new CMap() ); + + $name = uniqid(); + + $this->elements->itemAt('body')->add(uniqid(), $tag->toXML()); + + return $name; + } + /** + * + * Removes inserted tag name from specific section + * sections can be head, body, styles, and placemark + * @param string $tagName to be removed + * @param string $section where to remove the tag + */ + public function removeTag( $tagName, $section ){ + foreach($this->elements as $map) + { + if($map->itemAt($section)){ + $map->itemAt($section)->remove($tagName); + return true; + } + } + return false; + } + /** + * + * This method is to add style tags as arrays + * the style nodes are represented as the following: + *
    +	 * // Icon
    +	 * $nodes = array( 'href'=>'http://url');
    +	 * // Line
    +	 * $nodes = array( 'color'=>'#FFAA00','width'=>2);
    +	 * // Polyline 
    +	 * $nodes = array( 'color'=>'#FFAA00','colorMode'=>'random' );
    +	 * 
    + * @param string $styleId id of the style + * @param string $styleType the type of the style + * @param array $nodes the tags to insert + */ + public function addStyleArray( $styleId, $styleType = self::STYLE_ICON, $nodes = array() ){ + $item = ''; + + if( null === $this->elements->itemAt('styles') ) + $this->elements->add( 'styles', new CMap() ); + + $this->elements->itemAt('styles')->add( $styleId, $item ); + } + /** + * Adds a placemark on array structure + * Example: + *
    +	 * $nodes = array(
    +	 * 	'name'=>array('content'=>'testing'),
    +	 *	'description'=>array('content'=>'This marker has HTML'),
    +	 *	'styleUrl'=>array('content'=>'#style2'),
    +	 *	'Point'=>array('children'=>array(
    +	 *		'coordinates'=>array('content'=>'2.9087440013885635,39.719588117933185,0'))));
    +	 *
    +	 * 	 $kml->addPlacemark($nodes);
    +	 * 
    + * @param array $nodes the tags to insert + */ + public function addPlacemarkArray( $nodes = array() ){ + $item = ''; + + $item .= $this->batchNodeCreate($nodes); + + $item .= ''; + + if( null === $this->elements->itemAt('body') ) + $this->elements->add( 'body', new CMap() ); + + $name = uniqid(); + + $this->elements->itemAt('body')->add( $name, $item ); + + return $name; + + } + /** + * + * Converts array given tags to its XML representation + * Note: It does not check for correct array structure + * + * @param array $tags + */ + public function batchNodeCreate( $tags ){ + $result = ''; + if(is_array( $tags) ){ + foreach($tags as $tag=>$el){ + $result .= CHtml::openTag($tag, (isset($el['attributes']) && is_array($el['attributes'])? $el['attributes']:array())); + $result .= isset($el['content'])? ($tag=='description'? '': $el['content']) : ''; + $result .= isset($el['children']) && is_array($el['children'])? $this->batchNodeCreate($el['children']) : ''; + $result .= CHtml::closeTag($tag); + } + } + return $result; + } + /** + * + * Generates the feed + */ + public function generateFeed( ){ + // you can choose between the both, as both of them work correctly + // Google Earth MIME/TYPE + // header('Content-type: application/vnd.google-earth.kml+xml'); + header("Content-type: text/xml"); + + echo ''; + echo ''; + $this->renderItems(array('head', 'body','styles','placemarks')); + echo ''; + echo ''; + } + /** + * + * Render tags by sections + * @param array | string sections to render $sections + */ + public function renderItems( $sections ){ + + if(!is_array($sections)) $sections = array( $sections ); + + foreach( $sections as $section ){ + if(null === $this->elements->itemAt($section)) continue; + + foreach($this->elements->itemAt($section) as $tag) + echo $tag; + } + } +} diff --git a/extensions/EGMap/kml/EGMapKMLIconStyle.php b/extensions/EGMap/kml/EGMapKMLIconStyle.php new file mode 100644 index 0000000..1ca1779 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLIconStyle.php @@ -0,0 +1,74 @@ +tag = 'IconStyle'; + $this->id = $id; + $this->tagId = $iconId; + $this->href = $href; + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + + $icon = new EGMapKMLNode('Icon'); + if(!is_null($this->href)) + $icon->addChild( new EGMapKMLNode('href', $this->href ) ); + $this->addChild($icon); + $result = CHtml::openTag( 'Style', array( 'id'=>$this->id ) ); + $result .= parent::toXML(); + $result .= CHtml::closeTag('Style'); + + return $result; + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLLineString.php b/extensions/EGMap/kml/EGMapKMLLineString.php new file mode 100644 index 0000000..79b8bd0 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLLineString.php @@ -0,0 +1,86 @@ + to specify the color, color mode, and width + * of the line. When a LineString is extruded, the line is extended to the ground, forming a polygon that + * looks somewhat like a wall or fence. For extruded LineStrings, the line itself uses the current LineStyle, + * and the extrusion uses the current PolyStyle. + * + * @author Antonio Ramirez Cobos + * @link www.ramirezcobos.com + * + * + * @copyright + * + * Copyright (c) 2010 Antonio Ramirez Cobos + * 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. + * + */ +class EGMapKMLLineString extends EGMapKMLNode{ + /** + * + * Two or more coordinate tuples, each consisting of floating point values for longitude, latitude, and + * altitude. The altitude component is optional. Insert a space between tuples. + * Do not include spaces within a tuple. + * @var array + */ + protected $coordinates = array(); + /** + * + * Class constructor + * @param array $coordinates + */ + public function __construct( $coordinates = array() ){ + $this->tag = 'LineString'; + if(is_array($coordinates)) $this->coordinates = $coordinates; + } + /** + * + * Adds a coordenate to the array + * @param float | numeric string $latitude + * @param float | numeric string $longitude + * @param float | numeric string $elevation + */ + public function addCoordenate( $latitude, $longitude, $elevation ){ + $this->coordinates[] = $longitude.','.$latitude.','.$elevation; + } + /** + * + * Adds array of coordenates + * @param array $coords + * @throws CException + */ + public function addCoordenates( $coords ){ + if(!is_array( $coords) ) + throw new CException( Yii::t('EGMap','Coordinates parameter must be of type array and are required')); + foreach($coords as $coord){ + $this->coordinates[] = $coord; + } + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + $this->addChild(new EGMapKMLNode('altitudeMode','relative')); + $this->addChild( new EGMapKMLNode('coordinates', $this->coordinates) ); + return parent::toXML(); + } + +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLLineStyle.php b/extensions/EGMap/kml/EGMapKMLLineStyle.php new file mode 100644 index 0000000..3a6eae6 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLLineStyle.php @@ -0,0 +1,86 @@ +7fff0000, + * where alpha=0x7f, blue=0xff, green=0x00, and red=0x00. + * + * @var string color + */ + public $color; + /** + * + * Width of the line, in pixels. + * @var numeric string + */ + public $width; + /** + * + * Class constructor + * @param string $id + * @param string $lineId + * @param string $color + * @param string $width + */ + public function __construct($id, $lineId = null, $color = null, $width = null){ + + $this->tag = 'LineStyle'; + $this->id = $id; + $this->tagId = $lineId; + $this->color = $color; + $this->width = $width; + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + + $this->checkNode( 'color' ); + $this->checkNode( 'width' ); + + $result = CHtml::openTag( 'Style', array( 'id'=>$this->id ) ); + $result .= parent::toXML(); + $result .= CHtml::closeTag('Style'); + + return $result; + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLNode.php b/extensions/EGMap/kml/EGMapKMLNode.php new file mode 100644 index 0000000..1013ff5 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLNode.php @@ -0,0 +1,139 @@ +tag = $tag; + $this->content = $content; + } + /** + * + * Adds a child node to the tag + * @param EGMapKMLNode $node + */ + public function addChild( EGMapKMLNode $node ){ + $this->nodes[] = $node; + } + /** + * + * Clears all added children + */ + public function clearChildren(){ + $this->nodes = array(); + } + /** + * + * @return well formatted XML KML tags + */ + public function toXML(){ + $result = ''; + + if (is_array($this->attributes)) + $this->attributes['id'] = $this->tagId; + else $this->attributes = array(); + + $result .= CHtml::openTag($this->tag, (is_array($this->attributes)? $this->attributes:array())); + if(null !== $this->content && !empty($this->content)) + { + if($this->tag === 'description'){ + $result .= 'content.']]>'; + } + else if(is_array($this->content)){ + // arrays are separated by carriage return + // they can also be separated by spaces + $result .= implode(PHP_EOL, $this->content); + } + else + $result .= $this->content; + } + $result .= $this->renderChildren(); + $result .= CHtml::closeTag($this->tag); + + return $result; + } + /** + * + * Renders children tags + */ + protected function renderChildren() + { + $children = ''; + if( isset($this->nodes) && is_array($this->nodes) ){ + foreach( $this->nodes as $node ) + $children .= $node->toXML(); + } + return $children; + } + /** + * + * Checks if a node name property has null value + * if not then create a node + * @param string $node + */ + protected function checkNode( $node ){ + if(!is_null($this->$node)) + $this->nodes[] = new EGMapKMLNode($node, $this->$node); + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLPlacemark.php b/extensions/EGMap/kml/EGMapKMLPlacemark.php new file mode 100644 index 0000000..300df14 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLPlacemark.php @@ -0,0 +1,76 @@ +tag = 'Placemark'; + $this->name = $name; + $this->description = $description; + $this->styleUrl = $styleUrl; + $this->nodes = is_array($nodes)?$nodes:array(); + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + + $this->checkNode( 'name' ); + $this->checkNode( 'description' ); + $this->checkNode( 'styleUrl' ); + + return parent::toXML(); + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLPoint.php b/extensions/EGMap/kml/EGMapKMLPoint.php new file mode 100644 index 0000000..5212f4d --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLPoint.php @@ -0,0 +1,95 @@ + must be either relativeToGround, relativeToSeaFloor, or absolute. + * The point is extruded toward the center of the Earth's sphere. + * Enter description here ... + * @var boolean + */ + public $extrude; + /** + * + * Enter description here ... + * @param string $latitude + * @param string $longitude + * @param string $elevation + */ + public function __construct($latitude, $longitude, $elevation = 0){ + $this->tag = 'Point'; + $this->latitude = $latitude; + $this->longitude = $longitude; + $this->elevation = $elevation; + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + $this->checkNode('extrude'); + /** + * coordinate + * A single tuple consisting of floating point values for longitude, latitude, and altitude (in that order). + * Longitude and latitude values are in degrees + * altitude values (optional) are in meters above sea level + * Do not include spaces between the three values that describe a coordinate. + */ + if(!is_null($this->latitude) && !is_null($this->longitude)) + $this->addChild(new EGMapKMLNode('coordinates', $this->longitude.','.$this->latitude.','.$this->elevation)); + + + return parent::toXML(); + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLPolyStyle.php b/extensions/EGMap/kml/EGMapKMLPolyStyle.php new file mode 100644 index 0000000..63d4f0f --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLPolyStyle.php @@ -0,0 +1,61 @@ + are normal (no effect) and random. A value of random applies a random linear scale to the base as follows. + * To achieve a truly random selection of colors, specify a base of white (ffffffff). + * If you specify a single color component (for example, a value of ff0000ff for red), + * random color values for that one component (red) will be selected. In this case, + * the values would range from 00 (black) to ff (full red). If you specify values for two or + * for all three color components, a random linear scale is applied to each color component, + * with results ranging from black to the maximum values specified for each component. + * The opacity of a color comes from the alpha component of and is never randomized. + * @var string color Mode + */ + public $colorMode; + /** + * + * Class Constructor + * @param string $id + * @param string $polyId + * @param string $color + * @param string $colorMode + */ + public function __construct($id, $polyId = null, $color = null, $colorMode = null){ + $this->tag = 'PolyStyle'; + $this->id = $id; + $this->tagId = $polyId; + $this->color = $color; + $this->colorMode = $colorMode; + } +} \ No newline at end of file diff --git a/extensions/EGMap/kml/EGMapKMLPolygon.php b/extensions/EGMap/kml/EGMapKMLPolygon.php new file mode 100644 index 0000000..230dbb5 --- /dev/null +++ b/extensions/EGMap/kml/EGMapKMLPolygon.php @@ -0,0 +1,131 @@ + for their color, color mode, and fill. + * The for polygons must be specified in counterclockwise order. Polygons follow the "right-hand rule," + * which states that if you place the fingers of your right hand in the direction in which the coordinates are specified, + * your thumb points in the general direction of the geometric normal for the polygon. + * + * + * @author Antonio Ramirez Cobos + * @link www.ramirezcobos.com + * + * + * @copyright + * + * Copyright (c) 2010 Antonio Ramirez Cobos + * 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. + * + */ +class EGMapKMLPolygon extends EGMapKMLNode{ + /** + * + * // only supported here + * + * + * -122.366212,37.818977,30 + * -122.365424,37.819294,30 + * + * + * + * + * Another way + * + * + * + * + * -122.1,37.4,0 -122.0,37.4,0 -122.0,37.5,0 -122.1,37.5,0 -122.1,37.4,0 + * + * + * + * + * + * (required) + * Contains a element. + * + * Contains a element. + * A Polygon can contain multiple elements, + * which create multiple cut-outs inside the Polygon. + */ + /** + * + * Coordinates array + * @var array + */ + protected $coordinates = array(); + /** + * + * outerBoundaryIs = true + * innerBoundaryIs = false + * @var boolean + */ + protected $boundary; + + /** + * + * Enter description here ... + * @param boolean $outerBoundaryIs + * @param array $coordinates + */ + public function __construct( $outerBoundaryIs = false, $coordinates = array() ){ + + $this->tag = 'Polygon'; + $this->boundary = $outerBoundaryIs; + if(is_array($coordinates)) $this->coordinates = $coordinates; + } + /** + * + * Adds a coordenate to the array + * @param float | numeric string $latitude + * @param float | numeric string $longitude + * @param float | numeric string $elevation + */ + public function addCoordenate( $latitude, $longitude, $elevation ){ + $this->coordinates[] = $longitude.','.$latitude.','.$elevation; + } + /** + * + * Adds array of coordenates + * @param array $coords + * @throws CException + */ + public function addCoordenates( $coords ){ + if(!is_array( $coords) ) + throw new CException( Yii::t('EGMap','Coordinates parameter must be of type array')); + foreach($coords as $coord){ + $this->coordinates[] = $coord; + } + } + /** + * (non-PHPdoc) + * @see EGMapKMLNode::toXML() + */ + public function toXML(){ + $node = new EGMapKMLNode('LinearRing'); + $node->addChild(new EGMapKMLNode('coordinates', $this->coordinates )); + $parentNode = new EGMapKMLNode(($this->boundary?'outerBoundaryIs':'innerBoundaryIs')); + $parentNode->addChild( $node ); + $this->addChild( $parentNode ); + + return parent::toXML(); + } + +} \ No newline at end of file diff --git a/extensions/EGeoIp/EGeoIP.php b/extensions/EGeoIp/EGeoIP.php new file mode 100644 index 0000000..5a87003 --- /dev/null +++ b/extensions/EGeoIp/EGeoIP.php @@ -0,0 +1,232 @@ +_data = new CMap(); + } + /** + * + * Locates IP information + * @param string $ip address. If null, it will locate the IP of request + */ + public function locate( $ip = null ){ + if( null === $ip ) $ip = $_SERVER['REMOTE_ADDR']; + + $host = str_replace('{IP}',$ip, $this->_service); + $host = str_replace('{CURRENCY}', $this->_currency, $host ); + + $response = $this->fetch($host); + + if(!is_null( $response) && is_array($response)) + { + $this->_data->mergeWith($response); + $this->_data->add('ip',$ip); + return true; + } + return true; + } + /** + * + * Converts an amount to the located currency by using the + * located currency converter value. This function can only be + * used after a call to locate function has been executed. + * @param float | integer $amount + * @param integer $float number of decimals + * @param boolean $symbol to display the currency symbol or not (true by default) + */ + public function currencyConvert($amount, $float=2, $symbol=true) { + + if( null === $this->getCurrencyConverter() || !is_numeric($amount) ) + return false; + + $converted = round( ($amount * $this->getCurrencyConverter()), $float ); + if ( $symbol === true ) { + if($this->getCurrencySymbol() === 'USD') + $converted = $this->getCurrencySymbol().$converted; + else $converted .= $this->getCurrencySymbol(); + } + + return $converted; + } + /** + * + * Set base property + * @param string $currency + * @link http://www.geoplugin.com/iso4217 + */ + public function setBaseCurrency( $currency = 'USD' ){ + $this->_currency = $currency; + } + /** + * + * Returns base currency + */ + public function getBaseCurrency(){ + return $this->_currency; + } + /** + * + * Returns located IP + */ + public function getIp(){ + return $this->_data->itemAt('ip'); + } + /** + * + * Returns located City + */ + public function getCity(){ + return $this->_data->itemAt('geoplugin_city'); + } + /** + * + * Returns located region + */ + public function getRegion(){ + return $this->_data->itemAt('geoplugin_region'); + } + /** + * + * Returns located area code + */ + public function getAreaCode(){ + return $this->_data->itemAt('geoplugin_areaCode'); + } + /** + * + * Returns located DMA code + */ + public function getDma(){ + return $this->_data->itemAt('geoplugin_dmaCode'); + } + /** + * + * Returns located area code + */ + public function getCountryCode(){ + return $this->_data->itemAt('geoplugin_countryCode'); + } + /** + * + * Returns located country name + */ + public function getCountryName(){ + return $this->_data->itemAt('geoplugin_countryName'); + } + /** + * + * Returns located continent code + */ + public function getContinentCode(){ + return $this->_data->itemAt('geoplugin_continentCode'); + } + /** + * + * Returns located latitude + */ + public function getLatitude(){ + return $this->_data->itemAt('geoplugin_latitude'); + } + /** + * + * Returns located longitude + */ + public function getLongitude(){ + return $this->_data->itemAt('geoplugin_longitude'); + } + /** + * + * Returns located currency code + */ + public function getCurrencyCode(){ + return $this->_data->itemAt('geoplugin_currencyCode'); + } + /** + * + * Returns located currency symbol + */ + public function getCurrencySymbol(){ + return $this->_data->itemAt('geoplugin_currencySymbol'); + } + /** + * + * Returns located currency converter + */ + public function getCurrencyConverter(){ + return $this->_data->itemAt('geoplugin_currencyConverter'); + } + /** + * + * Fetches a URI and returns the contents of the call + * EHttpClient could also be used + * + * @param string $host + * @see http://www.yiiframework.com/extension/ehttpclient/ + */ + protected function fetch($host){ + + $response = null; + + if ( function_exists('curl_init') ) { + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $host); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERAGENT, 'EGeoIP Yii Extension Class v1.0'); + $response = curl_exec($ch); + curl_close ($ch); + + } else if ( ini_get('allow_url_fopen') ) { + + $response = file_get_contents($host, 'r'); + } + + return unserialize($response); + } + +} \ No newline at end of file diff --git a/extensions/EGeoNameService/EGeoNameService.php b/extensions/EGeoNameService/EGeoNameService.php new file mode 100644 index 0000000..7253c53 --- /dev/null +++ b/extensions/EGeoNameService/EGeoNameService.php @@ -0,0 +1,550 @@ + array( + 'parameters'=>array('lat','lng') + ), + // Returns the children (admin divisions and populated places) for a given geonameId + // Info: http://www.geonames.org/export/place-hierarchy.html#children + // Expected Results: view-source:http://api.geonames.org/children?geonameId=3175395&username=demo + 'children' => array( + 'parameters'=>array('geonameId','maxRows'), + 'root' => 'geonames', + ), + // returns a list of cities and placenames in the bounding box, ordered by relevancy (capital/population) + // Info: http://www.geonames.org/export/JSON-webservices.html#citiesJSON + // Expected Results: view-source:http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=de&username=demo + 'cities' => array( + 'parameters'=>array('north','south','east','west','lang','maxRows'), + 'root' => 'geonames', + ), + // Result : returns the iso country code for the given latitude/longitude + // Info: http://www.geonames.org/export/web-services.html#countrycode + // Expected Results: view-source:http://api.geonames.org/countryCodeJSON?formatted=true&lat=47.03&lng=10.2&username=demo&style=full + 'countryCode' => array( + 'parameters'=>array('lat','lng','type','lang','radius') + ), + // Result : Country information : Capital, Population, Area in square km, Bounding Box of mainland (excluding offshore islands) + // Info: http://www.geonames.org/export/web-services.html#countryInfo + // Expected Results: view-source:http://api.geonames.org/countryInfoJSON?formatted=true&lang=it&country=DE&username=demo&style=full + 'countryInfo' => array( + 'parameters'=>array('country','lang'), + 'root' => 'geonames', + ), + // Result : returns the country and the administrative subdivison (state, province,...) for the given latitude/longitude + // Info: http://www.geonames.org/export/web-services.html#countrysubdiv + // Expected Results: view-source:http://api.geonames.org/countrySubdivisionJSON?formatted=true&lat=47.03&lng=10.2&username=demo&style=full + 'countrySubdivision' => array( + 'parameters'=>array('lat','lng','lang','radius','level') + ), + // Result : returns a list of earthquakes, ordered by magnitude + // Info: http://www.geonames.org/export/JSON-webservices.html#earthquakesJSON + // Expected Results: view-source:http://api.geonames.org/earthquakesJSON?formatted=true&north=44.1&south=-9.9&east=-22.4&west=55.2&username=demo&style=full + 'earthquakes' => array( + 'parameters'=>array('north','south','east','west','minMagnitude','maxRows'), // 'date' cannot be empty, please check info + 'root' => 'earthquakes', + ), + /* 'extendedFindNearby' => array( + 'output' => 'xml', // not supported + ),*/ + // Result : returns the closest toponym for the lat/lng query as xml document + // Info: http://www.geonames.org/export/web-services.html#findNearby + // Expected Results: view-source:http://api.geonames.org/findNearbyJSON?formatted=true&lat=48.865618158309374&lng=2.344207763671875&fclass=P&fcode=PPLA&fcode=PPL&fcode=PPLC&username=demo&style=full + 'findNearby' => array( + // http://www.geonames.org/export/codes.html -featureCode / featureClass + // + 'parameters'=>array('lat','lng','featureClass','featureCode','radius','style','maxRows'), + 'root' => 'geonames', + ), + // Result : returns the closest populated place for the lat/lng query as xml document. The unit of the distance element is 'km'. + // Info: http://www.geonames.org/export/web-services.html#findNearbyPlaceName + // Expected Results: view-source:http://api.geonames.org/findNearbyPlaceNameJSON?formatted=true&lat=47.3&lng=9&username=demo&style=full + 'findNearbyPlaceName' => array( + 'parameters'=>array('lat','lng','lang'=>'en','style','radius','maxRows'), + 'root' => 'geonames', + ), + // Result : returns a list of postalcodes and places for the lat/lng query as xml document. + // Info: http://www.geonames.org/export/web-services.html#findNearbyPostalCodes + // Expected Results: view-source:http://api.geonames.org/findNearbyPostalCodesJSON?formatted=true&postalcode=8775&country=CH&radius=10&username=demo&style=full + 'findNearbyPostalCodes' => array( + 'parameters'=>array('lat','lng','radius','maxRows','style','country','localCountry','postalcode'), // or postalcode,country, radius (in Km), maxRows (default = 5) + 'root' => 'postalCodes', + ), + // US Only + // Result : returns the nearest street segments for the given latitude/longitude + // Info: http://www.geonames.org/maps/us-reverse-geocoder.html#findNearbyStreets + // Expected Results: view-source:http://api.geonames.org/findNearbyStreetsJSON?formatted=true&lat=37.451&lng=-122.18&username=demo&style=full + 'findNearbyStreets' => array( + 'parameters'=>array('lat','lng'), + 'root' => 'streetSegment', + ), + // Result : returns the nearest street segments for the given latitude/longitude + // Info: http://www.geonames.org/maps/osm-reverse-geocoder.html#findNearbyStreetsOSM + // Expected Results: view-source:http://api.geonames.org/findNearbyStreetsOSMJSON?formatted=true&lat=37.451&lng=-122.18&username=demo&style=full + 'findNearbyStreetsOSM' => array( + 'parameters'=>array('lat','lng'), + 'root' => 'streetSegment', + ), + // Result : returns a weather station with the most recent weather observation + // Info: http://www.geonames.org/export/JSON-webservices.html#findNearByWeatherJSON + // Expected Results + 'findNearByWeather' => array( + 'parameters' => array('lat','lng'), + 'root' => 'weatherObservation', + ), + // Result : returns a list of wikipedia entries + // Info: http://www.geonames.org/export/wikipedia-webservice.html#findNearbyWikipedia + // Expected Resuls: view-source:http://api.geonames.org/findNearbyWikipediaJSON?formatted=true&lat=47&lng=9&username=demo&style=full + 'findNearbyWikipedia' => array( + 'parameters' => array('lat','lng','radius','maxRows','lang'), + 'root' => 'geonames', + ), + // US Only + // Result : returns the nearest address for the given latitude/longitude, the street number is an 'educated guess' using an interpolation of street number at the end of a street segment. + // Info: http://www.geonames.org/maps/us-reverse-geocoder.html#findNearestAddress + // Expected Results: view-source:http://api.geonames.org/findNearestAddressJSON?formatted=true&lat=37.451&lng=-122.18&username=demo&style=full + 'findNearestAddress' => array( + 'parameters' => array('lat','lng'), + 'root' => 'address', + ), + // US Only + // Result : returns the nearest intersection for the given latitude/longitude + // Info: http://www.geonames.org/maps/us-reverse-geocoder.html#findNearestIntersection + // Expected Results: view-source:http://api.geonames.org/findNearestIntersectionJSON?formatted=true&lat=37.451&lng=-122.18&username=demo&style=full + 'findNearestIntersection' => array( + 'parameters' => array('lat','lng'), + 'root' => 'intersection', + ), + // Result : returns the nearest intersection for the given latitude/longitude + // Info: http://www.geonames.org/maps/osm-reverse-geocoder.html#findNearestIntersectionOSM + // Expected Results: view-source:http://api.geonames.org/findNearestIntersectionOSMJSON?formatted=true&lat=37.451&lng=-122.18&username=demo&style=full + 'findNearestIntersectionOSM' => array( + 'parameters' => array('lat','lng'), + 'root' => 'intersection', + ), + // Returns : geoname information for the given geonameId + // Info: none + // Expected Results: view-source:http://api.geonames.org/getJSON?formatted=true&geonameId=6295610&username=demo&style=full + 'get' => array( + 'parameters' => array('geonameId'), + ), + // GTOPO30 is a global digital elevation model (DEM) with a horizontal grid spacing of 30 arc seconds (approximately 1 kilometer). GTOPO30 was derived from several raster and vector sources of topographic information + // Info: http://www.geonames.org/export/web-services.html#gtopo30 + // Expected Results: view-source:http://api.geonames.org/gtopo30JSON?formatted=true&lat=47.01&lng=10.2&username=demo&style=full + 'gtopo30' => array( + 'parameters' => array('lat','lng'), + ), + // Result : returns a list of GeoName records, ordered by hierarchy level. The top hierarchy (continent) is the first element in the list + // Info: http://www.geonames.org/export/place-hierarchy.html#hierarchy + // Expected Results: view-source:http://api.geonames.org/hierarchyJSON?formatted=true&geonameId=2657896&username=demo&style=full + 'hierarchy' => array( + 'parameters' => array('geonameId'), + 'root' => 'geonames', + ), + // US Only + // Result : returns the neighbourhood for the given latitude/longitude + // Info: http://www.geonames.org/export/web-services.html#neighbourhood + // Expected Results: view-source:http://api.geonames.org/neighbourhoodJSON?formatted=true&lat=40.78343&lng=-73.96625&username=demo&style=full + 'neighbourhoud' => array( + 'parameters' => array('lat','lng'), + 'root' => 'neighbourhood', + ), + // Result : returns the neighbours of a toponym, currently only implemented for countries + // Info: http://www.geonames.org/export/place-hierarchy.html#neighbours + // Expected Results: view-source:http://api.geonames.org/neighboursJSON?formatted=true&geonameId=2658434&username=demo&style=full + 'neighbours' => array( + 'parameters' => array('geonameId'), + 'root' => 'geonames', + ), + // Result : returns the ocean or sea for the given latitude/longitude + // Info: http://www.geonames.org/export/web-services.html#ocean + // Expected Results: view-source:http://api.geonames.org/oceanJSON?formatted=true&lat=40.78343&lng=-43.96625&username=demo&style=full + 'ocean'=>array( + 'parameters' => array('lat','lng'), + 'root' => 'ocean' + ), + // Result : countries for which postal code geocoding is available. + // Info: http://www.geonames.org/export/web-services.html#postalCodeCountryInfo + // Expected Results: view-source:http://api.geonames.org/postalCodeCountryInfoJSON?formatted=true&&username=demo&style=full + 'postalCodeCountryInfo' => array( + 'root' => 'geonames', + ), + // Result : returns a list of places for the given postalcode in JSON format + // Info: /web-services.html#postalCodeLookupJSON + // Expected Results: view-source:http://api.geonames.org/postalCodeLookupJSON?formatted=true&postalcode=6600&country=AT&username=demo&style=full + 'postalCodeLookup' => array( + 'parameters' => array('postalcode','country' ,'maxRows','callback', 'charset'), + 'root' => 'postalcodes', + ), + // Result : returns a list of postal codes and places for the placename/postalcode query as xml document + // Info: http://www.geonames.org/export/web-services.html#postalCodeSearch + // Expected Results: view-source:http://api.geonames.org/postalCodeSearchJSON?formatted=true&postalcode=9011&maxRows=10&username=demo&style=full + 'postalCodeSearch' => array( + 'parameters' => array( 'postalcode','postalcode_startsWith','placename', + 'placename_startsWith','country','countryBias', + 'maxRows','style','operator','charset','isReduced'), + 'root' => 'postalCodes', + ), + // Result : returns the names found for the searchterm as xml or json document, the search is using an AND operator + // Info: http://www.geonames.org/export/geonames-search.html + // Expected Results: view-source:http://api.geonames.org/searchJSON?formatted=true&q=london&maxRows=10&lang=es&username=demo&style=full + 'search' => array( + 'parameters' => array( 'q','name','name_startsWith','name_equals','maxRows', + 'startRow','country','countryBias','continentCode', + 'adminCode1','adminCode2','adminCode3','featureClass', + 'featureCode','lang','type','style','isNameRequired', + 'tag','operator','charset','fuzzy'), + 'root' => 'geonames', + ), + // Result : Returns all siblings of a GeoNames toponym. + // Info: http://www.geonames.org/export/place-hierarchy.html#siblings + // Expected Results: view-source:http://api.geonames.org/siblingsJSON?formatted=true&geonameId=3017382&username=demo&style=full + 'siblings' => array( + 'parameters' => array('geonameId'), + 'root' => 'geonames', + ), + // Result : This web service is using Shuttle Radar Topography Mission (SRTM) data with data points located every 3-arc-second (approximately 90 meters) on a latitude/longitude grid + // Info: http://www.geonames.org/export/web-services.html#srtm3 + // Expected Results: view-source:http://api.geonames.org/srtm3JSON?formatted=true&lat=50.01&lng=10.2&username=demo&style=full + 'srtm3' => array( + 'parameters' => array('lat','lng'), + ), + // Result : the timezone at the lat/lng with gmt offset (1. January) and dst offset (1. July) + // Info: http://www.geonames.org/export/web-services.html#timezone + // Expected Results: view-source:http://api.geonames.org/timezoneJSON?formatted=true&lat=47.01&lng=10.2&username=demo&style=full + 'timezone' => array( + 'parameters' => array('lat','lng', 'radius', 'date'), + ), + // Result : returns a list of weather stations with the most recent weather observation + // Info: http://www.geonames.org/export/JSON-webservices.html#weatherJSON + // Expected Results: view-source:http://api.geonames.org/weatherJSON?formatted=true&north=44.1&south=-9.9&east=-22.4&west=55.2&username=demo&style=full + 'weather' => array( + 'parameters' => array('north','south','east','west','maxRows'), + 'root' => 'weatherObservations', + ), + // Result : returns the weather station and the most recent weather observation for the ICAO code + // Info: http://www.geonames.org/export/JSON-webservices.html#weatherIcaoJSON + // Expected Results: view-source:http://api.geonames.org/weatherIcaoJSON?formatted=true&ICAO=LSZH&username=demo&style=full + 'weatherIcao' => array( + 'parameters' => array('ICAO'), + 'root' => 'weatherObservation', + ), + // Result : returns the wikipedia entries within the bounding box + // Info: http://www.geonames.org/export/wikipedia-webservice.html#wikipediaBoundingBox + // Expected Results: view-source:http://api.geonames.org/wikipediaBoundingBoxJSON?formatted=true&north=44.1&south=-9.9&east=-22.4&west=55.2&username=demo&style=full + 'wikipediaBoundingBox' => array( + 'parameters' => array('north','south','east','west','maxRows','lang'), + 'root' => 'geonames', + ), + // Result : returns the wikipedia entries found for the searchterm + // Info: http://www.geonames.org/export/wikipedia-webservice.html#wikipediaSearch + // Expected Results: view-source:http://api.geonames.org/wikipediaSearchJSON?formatted=true&q=london&maxRows=10&username=demo&style=full + 'wikipediaSearch' => array( + 'parameters' => array('q','title','lang','maxRows'), + 'root' => 'geonames', + ), + ); + /** + * + * class constructor + * @param string $username optional + */ + public function __construct( $username='' ){ + $this->setUsername($username); + } + /** + * + * Property set username + * @param string $username sets the username to use with the webservice + */ + public function setUsername( $username ){ + $this->_username = $username; + } + /** + * + * Property get username + */ + public function getUsername(){ + return $this->_username; + } + /** + * + * Returns array of supported methods by the webservice + */ + public function getSupportedMethods(){ + return $this->_supportedMethods; + } + /** + * + * Returns the array of parameters for specific methdo + * @param unknown_type $method + */ + public function methodParams($method){ + if(!array_key_exists($method, $this->supportedMethods)) + return false; + return isset($this->supportedMethods[$method]['parameters'])?$this->supportedMethods[$method]['parameters']:array(); + } + /** + * (non-PHPdoc) + * @see CComponent::__get() + */ + public function __get($name){ + if(null !== $this->_results && isset($this->_results[$name])){ + return $this->_results[$name]; + } + + return parent::__get($name); + } + /** + * (non-PHPdoc) + * @see CComponent::__isset() + */ + public function __isset($name){ + if(isset($this->_results[$name])) return true; + + return parent::__isset($name); + } + /** + * (non-PHPdoc) + * @see CComponent::__call() + */ + public function __call($name, $parameters=array()){ + if(!array_key_exists($name, $this->supportedMethods)){ + return parent::__call($name, $parameters); + } + $count = count($parameters); + + $result = $this->_request($name, ($count?$parameters[0]:array())); + + $this->_results = $result; + + $this->_checkResponse(); + + if(isset($this->supportedMethods[$name]['root'])) $this->_parseRoot($this->supportedMethods[$name]['root']); + + // returns as array + return $result; + } + /** + * + * Parses the array to convert results to + * EGeoNameResult class to easy the access to + * the array of results. Only transforms first level + * of results, subsequent arrays are left as they are + * @param array $root + */ + private function _parseRoot($root){ + if(isset($this->_results[$root]) && is_array($this->_results[$root])){ + $e = current($this->_results[$root]); + if(is_array($e)){ + $cnt = count($this->_results[$root]); + for($i=0; $i<$cnt; $i++){ + $this->_results[$root][$i] = new EGeoNameResult($this->_results[$root][$i]); + } + }else $this->_results[$root] = new EGeoNameResult($this->_results[$root]); + } + } + /** + * + * Checks for webervice errors + * @throws CException + */ + private function _checkResponse(){ + if (isset($this->_results['status']['value']) + && isset($this->_results['status']['message'])) { + + throw new CException('Geonames error response: ' . + $this->_results['status']['message'], + $this->_results['status']['value'] + ); + } + } + /** + * + * Calls webservice + * @param string $method name to make the request + * @param array $parameters + */ + private function _request($method, $parameters) + { + if(!is_array($parameters)) $params = split('&', $parameters); + + $parameters = array_merge(array_fill_keys($this->methodParams($method),null),$parameters); + $pairs = ''; + + + $uri = $this->_api.$method.'JSON'; + + if(null !== $this->getUsername()){ + $parameters['username'] = $this->getUsername(); + } + + foreach($parameters as $key=>$val){ + if(null === $val) continue; + $pairs[] = urlencode($key).'='.urlencode($val); + } + + if( function_exists('curl_version') ){ + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $uri); + curl_setopt($ch, CURLOPT_HEADER,0); + curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&',$pairs)); + + $raw_data = curl_exec($ch); + curl_close($ch); + } + else // no CUrl, try differently + $raw_data = file_get_contents($uri.'?'.implode('&',$pairs)); + + return CJSON::decode($raw_data); + } +} +/** + * + * EGeoNameResult + * + * Easies the access of EGeoNameService results by + * allowing array elements to be accessed as properties + * of an object + * @author antonio + * + */ +class EGeoNameResult { + /** + * + * Holds array elements of a result + * @var array + */ + protected $_vars = array(); + /** + * + * Class constructor + * @param array $props + */ + public function __construct( $props ){ + if(is_array($props)) + $this->_vars = $props; + } + /** + * + * Returns a property value. Do not call this method. This is a PHP magic method + * overrided to allow using the following syntax + *
    +	 * 	$egeoresult->propertyName;
    +	 * 
    + * @param string $name the property name + * @throws CException + */ + public function __get($name){ + if(array_key_exists($name, $this->_vars)) return $this->_vars[$name]; + + throw new CException(Yii::t('EGeoNames','Property "{class}.{property}" is not defined.', + array('{class}'=>get_class($this), '{property}'=>$name))); + } + /** + * + * Checks if a property value is null. + * Do not call this method. This is a PHP magic method that we override + * to allow using isset() to detect if a component property is set or not. + * @param string $name the property to check + */ + public function __isset($name){ + return isset($this->_vars[$name]); + } +} \ No newline at end of file diff --git a/extensions/EHttpClient/ECookieJar.php b/extensions/EHttpClient/ECookieJar.php new file mode 100644 index 0000000..86a4a82 --- /dev/null +++ b/extensions/EHttpClient/ECookieJar.php @@ -0,0 +1,353 @@ +getDomain(); + $path = $cookie->getPath(); + if (! isset($this->cookies[$domain])) $this->cookies[$domain] = array(); + if (! isset($this->cookies[$domain][$path])) $this->cookies[$domain][$path] = array(); + $this->cookies[$domain][$path][$cookie->getName()] = $cookie; + } else { + + throw new CException('Supplient argument is not a valid cookie string or object'); + } + } + + /** + * Parse an HTTP response, adding all the cookies set in that response + * to the cookie jar. + * + * @param EHttpResponse $response + * @param EUriHttp|string $ref_uri Requested URI + */ + public function addCookiesFromResponse($response, $ref_uri) + { + if (! $response instanceof EHttpResponse) { + + throw new CException('$response is expected to be a Response object, ' . + gettype($response) . ' was passed'); + } + + $cookie_hdrs = $response->getHeader('Set-Cookie'); + + if (is_array($cookie_hdrs)) { + foreach ($cookie_hdrs as $cookie) { + $this->addCookie($cookie, $ref_uri); + } + } elseif (is_string($cookie_hdrs)) { + $this->addCookie($cookie_hdrs, $ref_uri); + } + } + + /** + * Get all cookies in the cookie jar as an array + * + * @param int $ret_as Whether to return cookies as objects of EHttpCookieJar or as strings + * @return array|string + */ + public function getAllCookies($ret_as = self::COOKIE_OBJECT) + { + $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as); + return $cookies; + } + + /** + * Return an array of all cookies matching a specific request according to the request URI, + * whether session cookies should be sent or not, and the time to consider as "now" when + * checking cookie expiry time. + * + * @param string|EUriHttp $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $ret_as Whether to return cookies as objects of EHttpCookieJar or as strings + * @param int $now Override the current time when checking for expiry time + * @return array|string + */ + public function getMatchingCookies($uri, $matchSessionCookies = true, + $ret_as = self::COOKIE_OBJECT, $now = null) + { + if (is_string($uri)) $uri = EUri::factory($uri); + if (! $uri instanceof EUriHttp) { + throw new CException("Invalid URI string or object passed"); + } + + // Set path + $path = $uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + if (! $path) $path = '/'; + + // First, reduce the array of cookies to only those matching domain and path + $cookies = $this->_matchDomain($uri->getHost()); + $cookies = $this->_matchPath($cookies, $path); + $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT); + + // Next, run Cookie->match on all cookies to check secure, time and session mathcing + $ret = array(); + foreach ($cookies as $cookie) + if ($cookie->match($uri, $matchSessionCookies, $now)) + $ret[] = $cookie; + + // Now, use self::_flattenCookiesArray again - only to convert to the return format ;) + $ret = $this->_flattenCookiesArray($ret, $ret_as); + + return $ret; + } + + /** + * Get a specific cookie according to a URI and name + * + * @param EUriHttp|string $uri The uri (domain and path) to match + * @param string $cookie_name The cookie's name + * @param int $ret_as Whether to return cookies as objects of EHttpCookieJar or as strings + * @return EHttpCookieJar|string + */ + public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) + { + if (is_string($uri)) { + $uri = EUri::factory($uri); + } + + if (! $uri instanceof EUriHttp) { + throw new CException('Invalid URI specified'); + } + + // Get correct cookie path + $path = $uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + if (! $path) $path = '/'; + + if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) { + $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name]; + + switch ($ret_as) { + case self::COOKIE_OBJECT: + return $cookie; + break; + + case self::COOKIE_STRING_ARRAY: + case self::COOKIE_STRING_CONCAT: + return $cookie->__toString(); + break; + + default: + throw new CException("Invalid value passed for \$ret_as: {$ret_as}"); + break; + } + } else { + return false; + } + } + + /** + * Helper function to recursivly flatten an array. Shoud be used when exporting the + * cookies array (or parts of it) + * + * @param EHttpCookieJar|array $ptr + * @param int $ret_as What value to return + * @return array|string + */ + protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) { + if (is_array($ptr)) { + $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array()); + foreach ($ptr as $item) { + if ($ret_as == self::COOKIE_STRING_CONCAT) { + $ret .= $this->_flattenCookiesArray($item, $ret_as); + } else { + $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as)); + } + } + return $ret; + } elseif ($ptr instanceof EHttpCookie) { + switch ($ret_as) { + case self::COOKIE_STRING_ARRAY: + return array($ptr->__toString()); + break; + + case self::COOKIE_STRING_CONCAT: + return $ptr->__toString(); + break; + + case self::COOKIE_OBJECT: + default: + return array($ptr); + break; + } + } + + return null; + } + + /** + * Return a subset of the cookies array matching a specific domain + * + * Returned array is actually an array of pointers to items in the $this->cookies array. + * + * @param string $domain + * @return array + */ + protected function _matchDomain($domain) { + $ret = array(); + + foreach (array_keys($this->cookies) as $cdom) { + $regex = "/" . preg_quote($cdom, "/") . "$/i"; + if (preg_match($regex, $domain)) $ret[$cdom] = &$this->cookies[$cdom]; + } + + return $ret; + } + + /** + * Return a subset of a domain-matching cookies that also match a specified path + * + * Returned array is actually an array of pointers to items in the $passed array. + * + * @param array $dom_array + * @param string $path + * @return array + */ + protected function _matchPath($domains, $path) { + $ret = array(); + if (substr($path, -1) != '/') $path .= '/'; + + foreach ($domains as $dom => $paths_array) { + foreach (array_keys($paths_array) as $cpath) { + $regex = "|^" . preg_quote($cpath, "|") . "|i"; + if (preg_match($regex, $path)) { + if (! isset($ret[$dom])) $ret[$dom] = array(); + $ret[$dom][$cpath] = &$paths_array[$cpath]; + } + } + } + + return $ret; + } + + /** + * Create a new ECookieJar object and automatically load into it all the + * cookies set in an Http_Response object. If $uri is set, it will be + * considered as the requested URI for setting default domain and path + * of the cookie. + * + * @param EHttpResponse $response HTTP Response object + * @param EUriHttp|string $uri The requested URI + * @return EHttpCookieJarJar + * @todo Add the $uri functionality. + */ + public static function fromResponse(EHttpResponse $response, $ref_uri) + { + $jar = new self(); + $jar->addCookiesFromResponse($response, $ref_uri); + return $jar; + } +} diff --git a/extensions/EHttpClient/EHostnameValidator.php b/extensions/EHttpClient/EHostnameValidator.php new file mode 100644 index 0000000..9423182 --- /dev/null +++ b/extensions/EHttpClient/EHostnameValidator.php @@ -0,0 +1,430 @@ + "It appears to be an IP address, but IP addresses are not allowed", + self::UNKNOWN_TLD => "It appears to be a DNS hostname but cannot match TLD against known list", + self::INVALID_DASH => "Appears to be a DNS hostname but contains a dash (-) in an invalid position", + self::INVALID_HOSTNAME_SCHEMA => "Appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'", + self::UNDECIPHERABLE_TLD => "Appears to be a DNS hostname but cannot extract TLD part", + self::INVALID_HOSTNAME => "It does not match the expected structure for a DNS hostname", + self::INVALID_LOCAL_NAME => "It does not appear to be a valid local network name", + self::LOCAL_NAME_NOT_ALLOWED => "It appears to be a local network name but local network names are not allowed", + self::NOT_IP_ADDRESS => "It does not appear to be a valid IP address" + ); + + + /** + * @var array + */ + protected $_messageVariables = array( + 'tld' => '_tld' + ); + + /** + * Allows Internet domain names (e.g., example.com) + */ + const ALLOW_DNS = 1; + + /** + * Allows IP addresses + */ + const ALLOW_IP = 2; + + /** + * Allows local network names (e.g., localhost, www.localdomain) + */ + const ALLOW_LOCAL = 4; + + /** + * Allows all types of hostnames + */ + const ALLOW_ALL = 7; + + /** + * Whether IDN domains are validated + * + * @var boolean + */ + private $_validateIdn = true; + + /** + * Whether TLDs are validated against a known list + * + * @var boolean + */ + private $_validateTld = true; + + /** + * Bit field of ALLOW constants; determines which types of hostnames are allowed + * + * @var integer + */ + protected $_allow; + + /** + * Bit field of CHECK constants; determines what additional hostname checks to make + * + * @var unknown_type + */ + // protected $_check; + + /** + * Array of valid top-level-domains + * + * @var array + * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain + */ + protected $_validTlds = array( + 'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', + 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', + 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', + 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', + 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', + 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', + 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', + 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', + 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', + 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', + 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', + 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', + 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', + 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', + 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', + 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', + 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', + 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', + 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', + 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', + 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', + 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', + 'tz', 'ua', 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', + 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', + 'zw' + ); + protected $_IdnChars = array( + 'at'=>'\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}\x{0161}\x{017E}', + 'ch'=>'\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}', + 'de'=>array('\x{00E1}\x{00E0}\x{0103}\x{00E2}\x{00E5}\x{00E4}\x{00E3}\x{0105}\x{0101}\x{00E6}\x{0107}', + '\x{0109}\x{010D}\x{010B}\x{00E7}\x{010F}\x{0111}\x{00E9}\x{00E8}\x{0115}\x{00EA}\x{011B}' , + '\x{00EB}\x{0117}\x{0119}\x{0113}\x{011F}\x{011D}\x{0121}\x{0123}\x{0125}\x{0127}\x{00ED}' , + '\x{00EC}\x{012D}\x{00EE}\x{00EF}\x{0129}\x{012F}\x{012B}\x{0131}\x{0135}\x{0137}\x{013A}' , + '\x{013E}\x{013C}\x{0142}\x{0144}\x{0148}\x{00F1}\x{0146}\x{014B}\x{00F3}\x{00F2}\x{014F}' , + '\x{00F4}\x{00F6}\x{0151}\x{00F5}\x{00F8}\x{014D}\x{0153}\x{0138}\x{0155}\x{0159}\x{0157}' , + '\x{015B}\x{015D}\x{0161}\x{015F}\x{0165}\x{0163}\x{0167}\x{00FA}\x{00F9}\x{016D}\x{00FB}' , + '\x{016F}\x{00FC}\x{0171}\x{0169}\x{0173}\x{016B}\x{0175}\x{00FD}\x{0177}\x{00FF}\x{017A}' , + '\x{017E}\x{017C}\x{00F0}\x{00FE}'), + 'fi'=>'\x{00E5}\x{00E4}\x{00F6}', + 'hu'=>'\x{00E1}\x{00E9}\x{00ED}\x{00F3}\x{00F6}\x{0151}\x{00FA}\x{00FC}\x{0171}', + 'li'=>'\x{00EO}-\x{00F6}\x{00F8}-\x{00FF}\x{0153}', + 'no'=>array('\x00E1\x00E0\x00E4\x010D\x00E7\x0111\x00E9\x00E8\x00EA\x\x014B' , + '\x0144\x00F1\x00F3\x00F2\x00F4\x00F6\x0161\x0167\x00FC\x017E\x00E6' , + '\x00F8\x00E5'), + 'se'=> '\x{00E5}\x{00E4}\x{00F6}\x{00FC}\x{00E9}' + ); + protected $_errors = array(); + + /** + * @var string + */ + protected $_tld; + + /** + * Sets validator options + * + * @param integer $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS) + * @param boolean $validateIdn OPTIONAL Set whether IDN domains are validated (default true) + * @param boolean $validateTld OPTIONAL Set whether the TLD element of a hostname is validated (default true) + * @return void + * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs + */ + public function __construct($allow = self::ALLOW_DNS, $validateIdn = true, $validateTld = true) + { + // Set allow options + $this->setAllow($allow); + + // Set validation options + $this->_validateIdn = $validateIdn; + $this->_validateTld = $validateTld; + + } + + /** + * Defined by Zend_Validate_Interface + * + * Returns true if and only if $value is a valid IP address + * + * @param mixed $value + * @return boolean + */ + public function isValidIp($value) + { + $valueString = (string) $value; + + // $this->_setValue($valueString); + + if (ip2long($valueString) === false) { + //$this->_error(); + return false; + } + + return true; + } + + public function getErrors() + { + return $this->_errors; + } + /** + * Returns the allow option + * + * @return integer + */ + public function getAllow() + { + return $this->_allow; + } + + /** + * Sets the allow option + * + * @param integer $allow + * @return EValidateHostname Provides a fluent interface + */ + public function setAllow($allow) + { + $this->_allow = $allow; + return $this; + } + + /** + * Set whether IDN domains are validated + * + * This only applies when DNS hostnames are validated + * + * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them + */ + public function setValidateIdn ($allowed) + { + $this->_validateIdn = (bool) $allowed; + } + + /** + * Set whether the TLD element of a hostname is validated + * + * This only applies when DNS hostnames are validated + * + * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them + */ + public function setValidateTld ($allowed) + { + $this->_validateTld = (bool) $allowed; + } + + + + /** + * Returns true if and only if the $value is a valid hostname with respect to the current allow option + * + * @param string $value + * @throws CException if a fatal error occurs for validation process + * @return boolean + */ + public function isValid($value) + { + $valueString = (string) $value; + + // $this->_setValue($valueString); + + // Check input against IP address schema + if ($this->isValidIp($valueString)) { + if (!($this->_allow & self::ALLOW_IP)) { + $this->_errors[self::IP_ADDRESS_NOT_ALLOWED] = $this->_messageTemplates[self::IP_ADDRESS_NOT_ALLOWED]; + return false; + } else{ + return true; + } + } + + // Check input against DNS hostname schema + $domainParts = explode('.', $valueString); + if ((count($domainParts) > 1) && (strlen($valueString) >= 4) && (strlen($valueString) <= 254)) { + $status = false; + + do { + // First check TLD + if (preg_match('/([a-z]{2,10})$/i', end($domainParts), $matches)) { + + reset($domainParts); + + // Hostname characters are: *(label dot)(label dot label); max 254 chars + // label: id-prefix [*ldh{61} id-prefix]; max 63 chars + // id-prefix: alpha / digit + // ldh: alpha / digit / dash + + // Match TLD against known list + $this->_tld = strtolower($matches[1]); + if ($this->_validateTld) { + if (!in_array($this->_tld, $this->_validTlds)) { + $this->_error[self::UNKNOWN_TLD] = $this->_messageTemplates[self::UNKNOWN_TLD]; + $status = false; + break; + } + } + + /** + * Match against IDN hostnames + * @see EValidateHostnameInterface + */ + $labelChars = 'a-z0-9'; + $utf8 = false; + + if ($this->_validateIdn) { + // Load additional characters + if(array_key_exists($this->_tld,$this->_IdnChars)){ + $labelChars .= is_scalar($this->_IdnChars[$this->_tld])? $this->_IdnChars[$this->_tld]:join('',$this->_IdnChars[$this->_tld]); + $utf8 = true; + } + } + + // Keep label regex short to avoid issues with long patterns when matching IDN hostnames + $regexLabel = '/^[' . $labelChars . '\x2d]{1,63}$/i'; + if ($utf8) { + $regexLabel .= 'u'; + } + + // Check each hostname part + $valid = true; + foreach ($domainParts as $domainPart) { + + // Check dash (-) does not start, end or appear in 3rd and 4th positions + if (strpos($domainPart, '-') === 0 || + (strlen($domainPart) > 2 && strpos($domainPart, '-', 2) == 2 && strpos($domainPart, '-', 3) == 3) || + strrpos($domainPart, '-') === strlen($domainPart) - 1) { + + $this->_errors[self::INVALID_DASH] = $this->_messageTemplates[self::INVALID_DASH]; + $status = false; + break 2; + } + + // Check each domain part + $status = @preg_match($regexLabel, $domainPart); + if ($status === false) { + /** + * Regex error + * @see CException + */ + + throw new CException('Internal error: DNS validation failed'); + } elseif ($status === 0) { + $valid = false; + } + } + + // If all labels didn't match, the hostname is invalid + if (!$valid) { + $this->_errors[self::INVALID_HOSTNAME_SCHEMA] = $this->_messageTemplates[self::INVALID_HOSTNAME_SCHEMA]; + $status = false; + } + + } else { + // Hostname not long enough + $this->_errors[self::UNDECIPHERABLE_TLD] = $this->_messageTemplates[self::UNDECIPHERABLE_TLD]; + $status = false; + } + } while (false); + + // If the input passes as an Internet domain name, and domain names are allowed, then the hostname + // passes validation + if ($status && ($this->_allow & self::ALLOW_DNS)) { + return true; + } + } else { + $this->_errors[self::INVALID_HOSTNAME] = $this->_messageTemplates[self::INVALID_HOSTNAME]; + } + + // Check input against local network name schema; last chance to pass validation + $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}){1,254}$/'; + $status = @preg_match($regexLocal, $valueString); + if (false === $status) { + /** + * Regex error + * @see CException + */ + + throw new CException('Internal error: local network name validation failed'); + } + + // If the input passes as a local network name, and local network names are allowed, then the + // hostname passes validation + $allowLocal = $this->_allow & self::ALLOW_LOCAL; + if ($status && $allowLocal) { + return true; + } + + // If the input does not pass as a local network name, add a message + if (!$status) { + $this->_errors[self::INVALID_LOCAL_NAME] = $this->_messageTemplates[self::INVALID_LOCAL_NAME]; + } + + // If local network names are not allowed, add a message + if ($status && !$allowLocal) { + $this->_errors[self::LOCAL_NAME_NOT_ALLOWED] = $this->_messageTemplates[self::LOCAL_NAME_NOT_ALLOWED]; + } + + return false; + } + +} diff --git a/extensions/EHttpClient/EHttpClient.php b/extensions/EHttpClient/EHttpClient.php new file mode 100644 index 0000000..05f7272 --- /dev/null +++ b/extensions/EHttpClient/EHttpClient.php @@ -0,0 +1,1148 @@ + 5, + 'strictredirects' => false, + 'useragent' => 'EHttpClient', + 'timeout' => 10, + 'adapter' => 'EHttpClientAdapterSocket', + 'httpversion' => self::HTTP_1, + 'keepalive' => false, + 'storeresponse' => true, + 'strict' => true + ); + + /** + * The adapter used to preform the actual connection to the server + * + * @var EHttpClientAdapterInterface + */ + protected $adapter = null; + + /** + * Request URI + * + * @var EUriHttp + */ + protected $uri; + + /** + * Associative array of request headers + * + * @var array + */ + protected $headers = array(); + + /** + * HTTP request method + * + * @var string + */ + protected $method = self::GET; + + /** + * Associative array of GET parameters + * + * @var array + */ + protected $paramsGet = array(); + + /** + * Assiciative array of POST parameters + * + * @var array + */ + protected $paramsPost = array(); + + /** + * Request body content type (for POST requests) + * + * @var string + */ + protected $enctype = null; + + /** + * The raw post data to send. Could be set by setRawData($data, $enctype). + * + * @var string + */ + protected $raw_post_data = null; + + /** + * HTTP Authentication settings + * + * Expected to be an associative array with this structure: + * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic') + * Where 'type' should be one of the supported authentication types (see the AUTH_* + * constants), for example 'basic' or 'digest'. + * + * If null, no authentication will be used. + * + * @var array|null + */ + protected $auth; + + /** + * File upload arrays (used in POST requests) + * + * An associative array, where each element is of the format: + * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents') + * + * @var array + */ + protected $files = array(); + + /** + * The client's cookie jar + * + * @var EHttpCookieJar + */ + protected $cookiejar = null; + + /** + * The last HTTP request sent by the client, as string + * + * @var string + */ + protected $last_request = null; + + /** + * The last HTTP response received by the client + * + * @var EHttpResponse + */ + protected $last_response = null; + + /** + * Redirection counter + * + * @var int + */ + protected $redirectCounter = 0; + + /** + * Fileinfo magic database resource + * + * This varaiable is populated the first time _detectFileMimeType is called + * and is then reused on every call to this method + * + * @var resource + */ + static protected $_fileInfoDb = null; + + /** + * Contructor method. Will create a new HTTP client. Accepts the target + * URL and optionally configuration array. + * + * @param EUriHttp|string $uri + * @param array $config Configuration key-value pairs. + */ + public function __construct($uri = null, $config = null) + { + if ($uri !== null) $this->setUri($uri); + if ($config !== null) $this->setConfig($config); + } + + /** + * Set the URI for the next request + * + * @param EUriHttp|string $uri + * @return EHttpClient + * @throws EHttpClientException + */ + public function setUri($uri) + { + if (is_string($uri)) { + $uri = EUri::factory($uri); + } + + if (!$uri instanceof EUriHttp) { + throw new EHttpClientException( + Yii::t('EHttpClient','Passed parameter is not a valid HTTP URI.')); + } + + // We have no ports, set the defaults + if (! $uri->getPort()) { + $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80)); + } + + $this->uri = $uri; + + return $this; + } + + /** + * Get the URI for the next request + * + * @param boolean $as_string If true, will return the URI as a string + * @return EUriHttp|string + */ + public function getUri($as_string = false) + { + if ($as_string && $this->uri instanceof EUriHttp) { + return $this->uri->__toString(); + } else { + return $this->uri; + } + } + + /** + * Set configuration parameters for this HTTP client + * + * @param array $config + * @return EHttpClient + * @throws EHttpClientException + */ + public function setConfig($config = array()) + { + if (! is_array($config)) { + throw new EHttpClientException( + Yii::t('EHttpClient','Expected array parameter, given ' . gettype($config))); + } + + foreach ($config as $k => $v) + $this->config[strtolower($k)] = $v; + + return $this; + } + + /** + * Set the next request's method + * + * Validated the passed method and sets it. If we have files set for + * POST requests, and the new method is not POST, the files are silently + * dropped. + * + * @param string $method + * @return EHttpClient + * @throws EHttpClientException + */ + public function setMethod($method = self::GET) + { + $regex = '/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/'; + if (! preg_match($regex, $method)) { + throw new EHttpClientException( + Yii::t('EHttpClient',"'{$method}' is not a valid HTTP request method.")); + } + + if ($method == self::POST && $this->enctype === null) + $this->setEncType(self::ENC_URLENCODED); + + $this->method = $method; + + return $this; + } + + /** + * Set one or more request headers + * + * This function can be used in several ways to set the client's request + * headers: + * 1. By providing two parameters: $name as the header to set (eg. 'Host') + * and $value as it's value (eg. 'www.example.com'). + * 2. By providing a single header string as the only parameter + * eg. 'Host: www.example.com' + * 3. By providing an array of headers as the first parameter + * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case + * the function will call itself recursively for each array item. + * + * @param string|array $name Header name, full header string ('Header: value') + * or an array of headers + * @param mixed $value Header value or null + * @return EHttpClient + * @throws EHttpClientException + */ + public function setHeaders($name, $value = null) + { + // If we got an array, go recusive! + if (is_array($name)) { + foreach ($name as $k => $v) { + if (is_string($k)) { + $this->setHeaders($k, $v); + } else { + $this->setHeaders($v, null); + } + } + } else { + // Check if $name needs to be split + if ($value === null && (strpos($name, ':') > 0)) + list($name, $value) = explode(':', $name, 2); + + // Make sure the name is valid if we are in strict mode + if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) { + throw new EHttpClientException( + Yii::t('EHttpClient',"{$name} is not a valid HTTP header name")); + } + + $normalized_name = strtolower($name); + + // If $value is null or false, unset the header + if ($value === null || $value === false) { + unset($this->headers[$normalized_name]); + + // Else, set the header + } else { + // Header names are storred lowercase internally. + if (is_string($value)) $value = trim($value); + $this->headers[$normalized_name] = array($name, $value); + } + } + + return $this; + } + + /** + * Get the value of a specific header + * + * Note that if the header has more than one value, an array + * will be returned. + * + * @param string $key + * @return string|array|null The header value or null if it is not set + */ + public function getHeader($key) + { + $key = strtolower($key); + if (isset($this->headers[$key])) { + return $this->headers[$key][1]; + } else { + return null; + } + } + + /** + * Set a GET parameter for the request. Wrapper around _setParameter + * + * @param string|array $name + * @param string $value + * @return EHttpClient + */ + public function setParameterGet($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $k => $v) + $this->_setParameter('GET', $k, $v); + } else { + $this->_setParameter('GET', $name, $value); + } + + return $this; + } + + /** + * Set a POST parameter for the request. Wrapper around _setParameter + * + * @param string|array $name + * @param string $value + * @return EHttpClient + */ + public function setParameterPost($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $k => $v) + $this->_setParameter('POST', $k, $v); + } else { + $this->_setParameter('POST', $name, $value); + } + + return $this; + } + + /** + * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost + * + * @param string $type GET or POST + * @param string $name + * @param string $value + * @return null + */ + protected function _setParameter($type, $name, $value) + { + $parray = array(); + $type = strtolower($type); + switch ($type) { + case 'get': + $parray = &$this->paramsGet; + break; + case 'post': + $parray = &$this->paramsPost; + break; + } + + if ($value === null) { + if (isset($parray[$name])) unset($parray[$name]); + } else { + $parray[$name] = $value; + } + } + + /** + * Get the number of redirections done on the last request + * + * @return int + */ + public function getRedirectionsCount() + { + return $this->redirectCounter; + } + + /** + * Set HTTP authentication parameters + * + * $type should be one of the supported types - see the self::AUTH_* + * constants. + * + * To enable authentication: + * + * $this->setAuth('shahar', 'secret', EHttpClient::AUTH_BASIC); + * + * + * To disable authentication: + * + * $this->setAuth(false); + * + * + * @see http://www.faqs.org/rfcs/rfc2617.html + * @param string|false $user User name or false disable authentication + * @param string $password Password + * @param string $type Authentication type + * @return EHttpClient + * @throws EHttpClientException + */ + public function setAuth($user, $password = '', $type = self::AUTH_BASIC) + { + // If we got false or null, disable authentication + if ($user === false || $user === null) { + $this->auth = null; + + // Else, set up authentication + } else { + // Check we got a proper authentication type + if (! defined('self::AUTH_' . strtoupper($type))) { + throw new CException("Invalid or not supported authentication type: '$type'"); + } + + $this->auth = array( + 'user' => (string) $user, + 'password' => (string) $password, + 'type' => $type + ); + } + + return $this; + } + + /** + * Set the HTTP client's cookie jar. + * + * A cookie jar is an object that holds and maintains cookies across HTTP requests + * and responses. + * + * @param EHttpCookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable + * @return EHttpClient + * @throws EHttpClientException + */ + public function setCookieJar($cookiejar = true) + { + if (! class_exists('ECookieJar')) + require_once 'ECookieJar.php'; + + if ($cookiejar instanceof ECookieJar) { + $this->cookiejar = $cookiejar; + } elseif ($cookiejar === true) { + $this->cookiejar = new ECookieJar(); + } elseif (! $cookiejar) { + $this->cookiejar = null; + } else { + throw new EHttpClientException( + Yii::t('EHttpClient','Invalid parameter type passed as CookieJar')); + } + + return $this; + } + + /** + * Return the current cookie jar or null if none. + * + * @return EHttpCookieJar|null + */ + public function getCookieJar() + { + return $this->cookiejar; + } + + /** + * Add a cookie to the request. If the client has no Cookie Jar, the cookies + * will be added directly to the headers array as "Cookie" headers. + * + * @param EHttpCookie|string $cookie + * @param string|null $value If "cookie" is a string, this is the cookie value. + * @return EHttpClient + * @throws EHttpClientException + */ + public function setCookie($cookie, $value = null) + { + if (! class_exists('EHttpCookie')) + require_once 'EHttpCookie.php'; + + if (is_array($cookie)) { + foreach ($cookie as $c => $v) { + if (is_string($c)) { + $this->setCookie($c, $v); + } else { + $this->setCookie($v); + } + } + + return $this; + } + + if ($value !== null) $value = urlencode($value); + + if (isset($this->cookiejar)) { + if ($cookie instanceof EHttpCookie) { + $this->cookiejar->addCookie($cookie); + } elseif (is_string($cookie) && $value !== null) { + $cookie = EHttpCookie::fromString("{$cookie}={$value}", $this->uri); + $this->cookiejar->addCookie($cookie); + } + } else { + if ($cookie instanceof EHttpCookie) { + $name = $cookie->getName(); + $value = $cookie->getValue(); + $cookie = $name; + } + + if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})")); + } + + $value = addslashes($value); + + if (! isset($this->headers['cookie'])) $this->headers['cookie'] = array('Cookie', ''); + $this->headers['cookie'][1] .= $cookie . '=' . $value . '; '; + } + + return $this; + } + + /** + * Set a file to upload (using a POST request) + * + * Can be used in two ways: + * + * 1. $data is null (default): $filename is treated as the name if a local file which + * will be read and sent. Will try to guess the content type using mime_content_type(). + * 2. $data is set - $filename is sent as the file name, but $data is sent as the file + * contents and no file is read from the file system. In this case, you need to + * manually set the content-type ($ctype) or it will default to + * application/octet-stream. + * + * @param string $filename Name of file to upload, or name to save as + * @param string $formname Name of form element to send as + * @param string $data Data to send (if null, $filename is read and sent) + * @param string $ctype Content type to use (if $data is set and $ctype is + * null, will be application/octet-stream) + * @return EHttpClient + * @throws EHttpClientException + */ + public function setFileUpload($filename, $formname, $data = null, $ctype = null) + { + if ($data === null) { + if (($data = @file_get_contents($filename)) === false) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Unable to read file '{$filename}' for upload")); + } + + if (! $ctype) $ctype = $this->_detectFileMimeType($filename); + } + + // Force enctype to multipart/form-data + $this->setEncType(self::ENC_FORMDATA); + + $this->files[$formname] = array(basename($filename), $ctype, $data); + + return $this; + } + + /** + * Set the encoding type for POST data + * + * @param string $enctype + * @return EHttpClient + */ + public function setEncType($enctype = self::ENC_URLENCODED) + { + $this->enctype = $enctype; + + return $this; + } + + /** + * Set the raw (already encoded) POST data. + * + * This function is here for two reasons: + * 1. For advanced user who would like to set their own data, already encoded + * 2. For backwards compatibilty: If someone uses the old post($data) method. + * this method will be used to set the encoded data. + * + * @param string $data + * @param string $enctype + * @return EHttpClient + */ + public function setRawData($data, $enctype = null) + { + $this->raw_post_data = $data; + $this->setEncType($enctype); + + return $this; + } + + /** + * Clear all GET and POST parameters + * + * Should be used to reset the request parameters if the client is + * used for several concurrent requests. + * + * @return EHttpClient + */ + public function resetParameters() + { + // Reset parameter data + $this->paramsGet = array(); + $this->paramsPost = array(); + $this->files = array(); + $this->raw_post_data = null; + + // Clear outdated headers + if (isset($this->headers['content-type'])) unset($this->headers['content-type']); + if (isset($this->headers['content-length'])) unset($this->headers['content-length']); + + return $this; + } + + /** + * Get the last HTTP request as string + * + * @return string + */ + public function getLastRequest() + { + return $this->last_request; + } + + /** + * Get the last HTTP response received by this client + * + * If $config['storeresponse'] is set to false, or no response was + * stored yet, will return null + * + * @return EHttpResponse or null if none + */ + public function getLastResponse() + { + return $this->last_response; + } + + /** + * Load the connection adapter + * + * While this method is not called more than one for a client, it is + * seperated from ->request() to preserve logic and readability + * + * @param EHttpClientAdapterInterface + * @return null + * @throws EHttpClientException + */ + public function setAdapter($adapter) + { + $adapter = new $adapter; + + if (! $adapter instanceof EHttpClientAdapterInterface) { + throw new EHttpClientException( + Yii::t('EHttpClient','Passed adapter is not a HTTP connection adapter')); + } + + $this->adapter = $adapter; + $config = $this->config; + unset($config['adapter']); + $this->adapter->setConfig($config); + } + + /** + * Send the HTTP request and return an HTTP response object + * + * @param string $method + * @return EHttpResponse + * @throws EHttpClientException + */ + public function request($method = null) + { + if (! $this->uri instanceof EUriHttp) { + throw new EHttpClientException( + Yii::t('EHttpClient','No valid URI has been passed to the client')); + } + + if ($method) $this->setMethod($method); + $this->redirectCounter = 0; + $response = null; + + // Make sure the adapter is loaded + + if ($this->adapter == null) $this->setAdapter($this->config['adapter']); + + // Send the first request. If redirected, continue. + do { + // Clone the URI and add the additional GET parameters to it + $uri = clone $this->uri; + if (! empty($this->paramsGet)) { + $query = $uri->getQuery(); + if (! empty($query)) $query .= '&'; + $query .= http_build_query($this->paramsGet, null, '&'); + + $uri->setQuery($query); + } + + $body = $this->_prepareBody(); + $headers = $this->_prepareHeaders(); + + // Open the connection, send the request and read the response + $this->adapter->connect($uri->getHost(), $uri->getPort(), + ($uri->getScheme() == 'https' ? true : false)); + + $this->last_request = $this->adapter->write($this->method, + $uri, $this->config['httpversion'], $headers, $body); + + $response = $this->adapter->read(); + if (! $response) { + throw new EHttpClientException( + Yii::t('EHttpClient','Unable to read response, or response is empty')); + } + + $response = EHttpResponse::fromString($response); + if ($this->config['storeresponse']) $this->last_response = $response; + + // Load cookies into cookie jar + if (isset($this->cookiejar)) $this->cookiejar->addCookiesFromResponse($response, $uri); + + // If we got redirected, look for the Location header + if ($response->isRedirect() && ($location = $response->getHeader('location'))) { + + // Check whether we send the exact same request again, or drop the parameters + // and send a GET request + if ($response->getStatus() == 303 || + ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || + $response->getStatus() == 301))) { + + $this->resetParameters(); + $this->setMethod(self::GET); + } + + // If we got a well formed absolute URI + if (EUriHttp::check($location)) { + $this->setHeaders('host', null); + $this->setUri($location); + + } else { + + // Split into path and query and set the query + if (strpos($location, '?') !== false) { + list($location, $query) = explode('?', $location, 2); + } else { + $query = ''; + } + $this->uri->setQuery($query); + + // Else, if we got just an absolute path, set it + if(strpos($location, '/') === 0) { + $this->uri->setPath($location); + + // Else, assume we have a relative path + } else { + // Get the current path directory, removing any trailing slashes + $path = $this->uri->getPath(); + $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); + $this->uri->setPath($path . '/' . $location); + } + } + ++$this->redirectCounter; + + } else { + // If we didn't get any location, stop redirecting + break; + } + + } while ($this->redirectCounter < $this->config['maxredirects']); + + return $response; + } + + /** + * Prepare the request headers + * + * @return array + */ + protected function _prepareHeaders() + { + $headers = array(); + + // Set the host header + if (! isset($this->headers['host'])) { + $host = $this->uri->getHost(); + + // If the port is not default, add it + if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) || + ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) { + $host .= ':' . $this->uri->getPort(); + } + + $headers[] = "Host: {$host}"; + } + + // Set the connection header + if (! isset($this->headers['connection'])) { + if (! $this->config['keepalive']) $headers[] = "Connection: close"; + } + + // Set the Accept-encoding header if not set - depending on whether + // zlib is available or not. + if (! isset($this->headers['accept-encoding'])) { + if (function_exists('gzinflate')) { + $headers[] = 'Accept-encoding: gzip, deflate'; + } else { + $headers[] = 'Accept-encoding: identity'; + } + } + + // Set the content-type header + if ($this->method == self::POST && + (! isset($this->headers['content-type']) && isset($this->enctype))) { + + $headers[] = "Content-type: {$this->enctype}"; + } + + // Set the user agent header + if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) { + $headers[] = "User-agent: {$this->config['useragent']}"; + } + + // Set HTTP authentication if needed + if (is_array($this->auth)) { + $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']); + $headers[] = "Authorization: {$auth}"; + } + + // Load cookies from cookie jar + if (isset($this->cookiejar)) { + $cookstr = $this->cookiejar->getMatchingCookies($this->uri, + true, ECookieJar::COOKIE_STRING_CONCAT); + + if ($cookstr) $headers[] = "Cookie: {$cookstr}"; + } + + // Add all other user defined headers + foreach ($this->headers as $header) { + list($name, $value) = $header; + if (is_array($value)) + $value = implode(', ', $value); + + $headers[] = "$name: $value"; + } + + return $headers; + } + + /** + * Prepare the request body (for POST and PUT requests) + * + * @return string + * @throws EHttpClientException + */ + protected function _prepareBody() + { + // According to RFC2616, a TRACE request should not have a body. + if ($this->method == self::TRACE) { + return ''; + } + + // If we have raw_post_data set, just use it as the body. + if (isset($this->raw_post_data)) { + $this->setHeaders('Content-length', strlen($this->raw_post_data)); + return $this->raw_post_data; + } + + $body = ''; + + // If we have files to upload, force enctype to multipart/form-data + if (count ($this->files) > 0) $this->setEncType(self::ENC_FORMDATA); + + // If we have POST parameters or files, encode and add them to the body + if (count($this->paramsPost) > 0 || count($this->files) > 0) { + switch($this->enctype) { + case self::ENC_FORMDATA: + // Encode body as multipart/form-data + $boundary = '---YIIHTTPCLIENT-' . md5(microtime()); + $this->setHeaders('Content-type', self::ENC_FORMDATA . "; boundary={$boundary}"); + + // Get POST parameters and encode them + $params = $this->_getParametersRecursive($this->paramsPost); + foreach ($params as $pp) { + $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); + } + + // Encode files + foreach ($this->files as $name => $file) { + $fhead = array('Content-type' => $file[1]); + $body .= self::encodeFormData($boundary, $name, $file[2], $file[0], $fhead); + } + + $body .= "--{$boundary}--\r\n"; + break; + + case self::ENC_URLENCODED: + // Encode body as application/x-www-form-urlencoded + $this->setHeaders('Content-type', self::ENC_URLENCODED); + $body = http_build_query($this->paramsPost, '', '&'); + break; + + default: + throw new EHttpClientException( + Yii::t('EHttpClient',"Cannot handle content type '{$this->enctype}' automatically." . + " Please use EHttpClient::setRawData to send this kind of content.")); + break; + } + } + + // Set the content-length if we have a body or if request is POST/PUT + if ($body || $this->method == self::POST || $this->method == self::PUT) { + $this->setHeaders('Content-length', strlen($body)); + } + + return $body; + } + + /** + * Helper method that gets a possibly multi-level parameters array (get or + * post) and flattens it. + * + * The method returns an array of (key, value) pairs (because keys are not + * necessarily unique. If one of the parameters in as array, it will also + * add a [] suffix to the key. + * + * @param array $parray The parameters array + * @param bool $urlencode Whether to urlencode the name and value + * @return array + */ + protected function _getParametersRecursive($parray, $urlencode = false) + { + if (! is_array($parray)) return $parray; + $parameters = array(); + + foreach ($parray as $name => $value) { + if ($urlencode) $name = urlencode($name); + + // If $value is an array, iterate over it + if (is_array($value)) { + $name .= ($urlencode ? '%5B%5D' : '[]'); + foreach ($value as $subval) { + if ($urlencode) $subval = urlencode($subval); + $parameters[] = array($name, $subval); + } + } else { + if ($urlencode) $value = urlencode($value); + $parameters[] = array($name, $value); + } + } + + return $parameters; + } + + /** + * Attempt to detect the MIME type of a file using available extensions + * + * This method will try to detect the MIME type of a file. If the fileinfo + * extension is available, it will be used. If not, the mime_magic + * extension which is deprected but is still available in many PHP setups + * will be tried. + * + * If neither extension is available, the default application/octet-stream + * MIME type will be returned + * + * @param string $file File path + * @return string MIME type + */ + protected function _detectFileMimeType($file) + { + $type = null; + + // First try with fileinfo functions + if (function_exists('finfo_open')) { + if (self::$_fileInfoDb === null) { + self::$_fileInfoDb = @finfo_open(FILEINFO_MIME); + } + + if (self::$_fileInfoDb) { + $type = finfo_file(self::$_fileInfoDb, $file); + } + + } elseif (function_exists('mime_content_type')) { + $type = mime_content_type($file); + } + + // Fallback to the default application/octet-stream + if (! $type) { + $type = 'application/octet-stream'; + } + + return $type; + } + + /** + * Encode data to a multipart/form-data part suitable for a POST request. + * + * @param string $boundary + * @param string $name + * @param mixed $value + * @param string $filename + * @param array $headers Associative array of optional headers @example ("Content-transfer-encoding" => "binary") + * @return string + */ + public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) { + $ret = "--{$boundary}\r\n" . + 'Content-disposition: form-data; name="' . $name .'"'; + + if ($filename) $ret .= '; filename="' . $filename . '"'; + $ret .= "\r\n"; + + foreach ($headers as $hname => $hvalue) { + $ret .= "{$hname}: {$hvalue}\r\n"; + } + $ret .= "\r\n"; + + $ret .= "{$value}\r\n"; + + return $ret; + } + + /** + * Create a HTTP authentication "Authorization:" header according to the + * specified user, password and authentication method. + * + * @see http://www.faqs.org/rfcs/rfc2617.html + * @param string $user + * @param string $password + * @param string $type + * @return string + * @throws EHttpClientException + */ + public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) + { + $authHeader = null; + + switch ($type) { + case self::AUTH_BASIC: + // In basic authentication, the user name cannot contain ":" + if (strpos($user, ':') !== false) { + throw new EHttpClientException( + Yii::t('EHttpClient',"The user name cannot contain ':' in 'Basic' HTTP authentication")); + } + + $authHeader = 'Basic ' . base64_encode($user . ':' . $password); + break; + + //case self::AUTH_DIGEST: + /** + * @todo Implement digest authentication + */ + // break; + + default: + throw new EHttpClientException( + Yii::t('EHttpClient',"Not a supported HTTP authentication type: '$type'")); + } + + return $authHeader; + } +} diff --git a/extensions/EHttpClient/EHttpCookie.php b/extensions/EHttpClient/EHttpCookie.php new file mode 100644 index 0000000..b77544f --- /dev/null +++ b/extensions/EHttpClient/EHttpCookie.php @@ -0,0 +1,335 @@ +name = (string) $name) { + throw new CException('Cookies must have a name'); + } + + if (! $this->domain = (string) $domain) { + throw new CException('Cookies must have a domain'); + } + + $this->value = (string) $value; + $this->expires = ($expires === null ? null : (int) $expires); + $this->path = ($path ? $path : '/'); + $this->secure = $secure; + } + + /** + * Get EHttpCookie name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get cookie value + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Get cookie domain + * + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Get the cookie path + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the expiry time of the cookie, or null if no expiry time is set + * + * @return int|null + */ + public function getExpiryTime() + { + return $this->expires; + } + + /** + * Check whether the cookie should only be sent over secure connections + * + * @return boolean + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Check whether the cookie has expired + * + * Always returns false if the cookie is a session cookie (has no expiry time) + * + * @param int $now Timestamp to consider as "now" + * @return boolean + */ + public function isExpired($now = null) + { + if ($now === null) $now = time(); + if (is_int($this->expires) && $this->expires < $now) { + return true; + } else { + return false; + } + } + + /** + * Check whether the cookie is a session cookie (has no expiry time set) + * + * @return boolean + */ + public function isSessionCookie() + { + return ($this->expires === null); + } + + /** + * Checks whether the cookie should be sent or not in a specific scenario + * + * @param string|EUriHttp $uri URI to check against (secure, domain, path) + * @param boolean $matchSessionCookies Whether to send session cookies + * @param int $now Override the current time when checking for expiry time + * @return boolean + */ + public function match($uri, $matchSessionCookies = true, $now = null) + { + if (is_string ($uri)) { + $uri = EUriHttp::factory($uri); + } + + // Make sure we have a valid EUriHttp object + if (! ($uri->valid() && ($uri->getScheme() == 'http' || $uri->getScheme() =='https'))) { + + throw new CException('Passed URI is not a valid HTTP or HTTPS URI'); + } + + // Check that the cookie is secure (if required) and not expired + if ($this->secure && $uri->getScheme() != 'https') return false; + if ($this->isExpired($now)) return false; + if ($this->isSessionCookie() && ! $matchSessionCookies) return false; + + // Validate domain and path + // Domain is validated using tail match, while path is validated using head match + $domain_preg = preg_quote($this->getDomain(), "/"); + if (! preg_match("/{$domain_preg}$/", $uri->getHost())) return false; + $path_preg = preg_quote($this->getPath(), "/"); + if (! preg_match("/^{$path_preg}/", $uri->getPath())) return false; + + // If we didn't die until now, return true. + return true; + } + + /** + * Get the cookie as a string, suitable for sending as a "EHttpCookie" header in an + * HTTP request + * + * @return string + */ + public function __toString() + { + return $this->name . '=' . urlencode($this->value) . ';'; + } + + /** + * Generate a new EHttpCookie object from a cookie string + * (for example the value of the Set-EHttpCookie HTTP header) + * + * @param string $cookieStr + * @param EUriHttp|string $ref_uri Reference URI for default values (domain, path) + * @return EHttpCookie A new EHttpCookie object or false on failure. + */ + public static function fromString($cookieStr, $ref_uri = null) + { + // Set default values + if (is_string($ref_uri)) { + $ref_uri = EUriHttp::factory($ref_uri); + } + + $name = ''; + $value = ''; + $domain = ''; + $path = ''; + $expires = null; + $secure = false; + $parts = explode(';', $cookieStr); + + // If first part does not include '=', fail + if (strpos($parts[0], '=') === false) return false; + + // Get the name and value of the cookie + list($name, $value) = explode('=', trim(array_shift($parts)), 2); + $name = trim($name); + $value = urldecode(trim($value)); + + // Set default domain and path + if ($ref_uri instanceof EUriHttp) { + $domain = $ref_uri->getHost(); + $path = $ref_uri->getPath(); + $path = substr($path, 0, strrpos($path, '/')); + } + + // Set other cookie parameters + foreach ($parts as $part) { + $part = trim($part); + if (strtolower($part) == 'secure') { + $secure = true; + continue; + } + + $keyValue = explode('=', $part, 2); + if (count($keyValue) == 2) { + list($k, $v) = $keyValue; + switch (strtolower($k)) { + case 'expires': + $expires = strtotime($v); + break; + case 'path': + $path = $v; + break; + case 'domain': + $domain = $v; + break; + default: + break; + } + } + } + + if ($name !== '') { + return new EHttpCookie($name, $value, $domain, $expires, $path, $secure); + } else { + return false; + } + } +} diff --git a/extensions/EHttpClient/EHttpResponse.php b/extensions/EHttpClient/EHttpResponse.php new file mode 100644 index 0000000..2337060 --- /dev/null +++ b/extensions/EHttpClient/EHttpResponse.php @@ -0,0 +1,631 @@ + 'Continue', + 101 => 'Switching Protocols', + + // Success 2xx + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + + // Redirection 3xx + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', // 1.1 + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + // 306 is deprecated but reserved + 307 => 'Temporary Redirect', + + // Client Error 4xx + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + + // Server Error 5xx + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 509 => 'Bandwidth Limit Exceeded' + ); + + /** + * The HTTP version (1.0, 1.1) + * + * @var string + */ + protected $version; + + /** + * The HTTP response code + * + * @var int + */ + protected $code; + + /** + * The HTTP response code as string + * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500) + * + * @var string + */ + protected $message; + + /** + * The HTTP response headers array + * + * @var array + */ + protected $headers = array(); + + /** + * The HTTP response body + * + * @var string + */ + protected $body; + + /** + * HTTP response constructor + * + * In most cases, you would use EHttpResponse::fromString to parse an HTTP + * response string and create a new EHttpResponse object. + * + * NOTE: The constructor no longer accepts nulls or empty values for the code and + * headers and will throw an exception if the passed values do not form a valid HTTP + * responses. + * + * If no message is passed, the message will be guessed according to the response code. + * + * @param int $code EHttpResponse code (200, 404, ...) + * @param array $headers Headers array + * @param string $body EHttpResponse body + * @param string $version HTTP version + * @param string $message EHttpResponse code as text + * @throws CException + */ + public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) + { + // Make sure the response code is valid and set it + if (self::responseCodeAsText($code) === null) { + throw new CException("{$code} is not a valid HTTP response code"); + } + + $this->code = $code; + + // Make sure we got valid headers and set them + if (! is_array($headers)) { + throw new CException('No valid headers were passed'); + } + + foreach ($headers as $name => $value) { + if (is_int($name)) + list($name, $value) = explode(": ", $value, 1); + + $this->headers[ucwords(strtolower($name))] = $value; + } + + // Set the body + $this->body = $body; + + // Set the HTTP version + if (! preg_match('|^\d\.\d$|', $version)) { + throw new CException("Invalid HTTP response version: $version"); + } + + $this->version = $version; + + // If we got the response message, set it. Else, set it according to + // the response code + if (is_string($message)) { + $this->message = $message; + } else { + $this->message = self::responseCodeAsText($code); + } + } + + /** + * Check whether the response is an error + * + * @return boolean + */ + public function isError() + { + $restype = floor($this->code / 100); + if ($restype == 4 || $restype == 5) { + return true; + } + + return false; + } + + /** + * Check whether the response in successful + * + * @return boolean + */ + public function isSuccessful() + { + $restype = floor($this->code / 100); + if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ??? + return true; + } + + return false; + } + + /** + * Check whether the response is a redirection + * + * @return boolean + */ + public function isRedirect() + { + $restype = floor($this->code / 100); + if ($restype == 3) { + return true; + } + + return false; + } + + /** + * Get the response body as string + * + * This method returns the body of the HTTP response (the content), as it + * should be in it's readable version - that is, after decoding it (if it + * was decoded), deflating it (if it was gzip compressed), etc. + * + * If you want to get the raw body (as transfered on wire) use + * $this->getRawBody() instead. + * + * @return string + */ + public function getBody() + { + $body = ''; + + // Decode the body if it was transfer-encoded + switch ($this->getHeader('transfer-encoding')) { + + // Handle chunked body + case 'chunked': + $body = self::decodeChunkedBody($this->body); + break; + + // No transfer encoding, or unknown encoding extension: + // return body as is + default: + $body = $this->body; + break; + } + + // Decode any content-encoding (gzip or deflate) if needed + switch (strtolower($this->getHeader('content-encoding'))) { + + // Handle gzip encoding + case 'gzip': + $body = self::decodeGzip($body); + break; + + // Handle deflate encoding + case 'deflate': + $body = self::decodeDeflate($body); + break; + + default: + break; + } + + return $body; + } + + /** + * Get the raw response body (as transfered "on wire") as string + * + * If the body is encoded (with Transfer-Encoding, not content-encoding - + * IE "chunked" body), gzip compressed, etc. it will not be decoded. + * + * @return string + */ + public function getRawBody() + { + return $this->body; + } + + /** + * Get the HTTP version of the response + * + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * Get the HTTP response status code + * + * @return int + */ + public function getStatus() + { + return $this->code; + } + + /** + * Return a message describing the HTTP response code + * (Eg. "OK", "Not Found", "Moved Permanently") + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Get the response headers + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Get a specific header as string, or null if it is not set + * + * @param string$header + * @return string|array|null + */ + public function getHeader($header) + { + $header = ucwords(strtolower($header)); + if (! is_string($header) || ! isset($this->headers[$header])) return null; + + return $this->headers[$header]; + } + + /** + * Get all headers as string + * + * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK") + * @param string $br Line breaks (eg. "\n", "\r\n", "
    ") + * @return string + */ + public function getHeadersAsString($status_line = true, $br = "\n") + { + $str = ''; + + if ($status_line) { + $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}"; + } + + // Iterate over the headers and stringify them + foreach ($this->headers as $name => $value) + { + if (is_string($value)) + $str .= "{$name}: {$value}{$br}"; + + elseif (is_array($value)) { + foreach ($value as $subval) { + $str .= "{$name}: {$subval}{$br}"; + } + } + } + + return $str; + } + + /** + * Get the entire response as string + * + * @param string $br Line breaks (eg. "\n", "\r\n", "
    ") + * @return string + */ + public function asString($br = "\n") + { + return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody(); + } + + /** + * A convenience function that returns a text representation of + * HTTP response codes. Returns 'Unknown' for unknown codes. + * Returns array of all codes, if $code is not specified. + * + * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown') + * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference + * + * @param int $code HTTP response code + * @param boolean $http11 Use HTTP version 1.1 + * @return string + */ + public static function responseCodeAsText($code = null, $http11 = true) + { + $messages = self::$messages; + if (! $http11) $messages[302] = 'Moved Temporarily'; + + if ($code === null) { + return $messages; + } elseif (isset($messages[$code])) { + return $messages[$code]; + } else { + return 'Unknown'; + } + } + + /** + * Extract the response code from a response string + * + * @param string $response_str + * @return int + */ + public static function extractCode($response_str) + { + preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m); + + if (isset($m[1])) { + return (int) $m[1]; + } else { + return false; + } + } + + /** + * Extract the HTTP message from a response + * + * @param string $response_str + * @return string + */ + public static function extractMessage($response_str) + { + preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m); + + if (isset($m[1])) { + return $m[1]; + } else { + return false; + } + } + + /** + * Extract the HTTP version from a response + * + * @param string $response_str + * @return string + */ + public static function extractVersion($response_str) + { + preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m); + + if (isset($m[1])) { + return $m[1]; + } else { + return false; + } + } + + /** + * Extract the headers from a response string + * + * @param string $response_str + * @return array + */ + public static function extractHeaders($response_str) + { + $headers = array(); + + // First, split body and headers + $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); + if (! $parts[0]) return $headers; + + // Split headers part to lines + $lines = explode("\n", $parts[0]); + unset($parts); + $last_header = null; + + foreach($lines as $line) { + $line = trim($line, "\r\n"); + if ($line == "") break; + + if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) { + unset($last_header); + $h_name = strtolower($m[1]); + $h_value = $m[2]; + + if (isset($headers[$h_name])) { + if (! is_array($headers[$h_name])) { + $headers[$h_name] = array($headers[$h_name]); + } + + $headers[$h_name][] = $h_value; + } else { + $headers[$h_name] = $h_value; + } + $last_header = $h_name; + } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) { + if (is_array($headers[$last_header])) { + end($headers[$last_header]); + $last_header_key = key($headers[$last_header]); + $headers[$last_header][$last_header_key] .= $m[1]; + } else { + $headers[$last_header] .= $m[1]; + } + } + } + + return $headers; + } + + /** + * Extract the body from a response string + * + * @param string $response_str + * @return string + */ + public static function extractBody($response_str) + { + $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); + if (isset($parts[1])) { + return $parts[1]; + } else { + return ''; + } + } + + /** + * Decode a "chunked" transfer-encoded body and return the decoded text + * + * @param string $body + * @return string + */ + public static function decodeChunkedBody($body) + { + $decBody = ''; + + while (trim($body)) { + if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) { + throw new CException("Error parsing body - doesn't seem to be a chunked message"); + } + + $length = hexdec(trim($m[1])); + $cut = strlen($m[0]); + + $decBody .= substr($body, $cut, $length); + $body = substr($body, $cut + $length + 2); + } + + return $decBody; + } + + /** + * Decode a gzip encoded message (when Content-encoding = gzip) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + */ + public static function decodeGzip($body) + { + if (! function_exists('gzinflate')) { + throw new CException('Unable to decode gzipped response ' . + 'body: perhaps the zlib extension is not loaded?'); + } + + return gzinflate(substr($body, 10)); + } + + /** + * Decode a zlib deflated message (when Content-encoding = deflate) + * + * Currently requires PHP with zlib support + * + * @param string $body + * @return string + */ + public static function decodeDeflate($body) + { + if (! function_exists('gzuncompress')) { + throw new CException('Unable to decode deflated response ' . + 'body: perhaps the zlib extension is not loaded?'); + } + + return gzuncompress($body); + } + + /** + * Create a new EHttpResponse object from a string + * + * @param string $response_str + * @return EHttpResponse + */ + public static function fromString($response_str) + { + $code = self::extractCode($response_str); + $headers = self::extractHeaders($response_str); + $body = self::extractBody($response_str); + $version = self::extractVersion($response_str); + $message = self::extractMessage($response_str); + + return new EHttpResponse($code, $headers, $body, $version, $message); + } +} diff --git a/extensions/EHttpClient/EUri.php b/extensions/EHttpClient/EUri.php new file mode 100644 index 0000000..5916387 --- /dev/null +++ b/extensions/EHttpClient/EUri.php @@ -0,0 +1,169 @@ +getUri(); + } + + /** + * Convenience function, checks that a $uri string is well-formed + * by validating it but not returning an object. Returns TRUE if + * $uri is a well-formed URI, or FALSE otherwise. + * + * @param string $uri The URI to check + * @return boolean + */ + public static function check($uri) + { + try { + $uri = self::factory($uri); + } catch (Exception $e) { + return false; + } + + return $uri->valid(); + } + + /** + * Create a new EUri object for a URI. If building a new URI, then $uri should contain + * only the scheme (http, ftp, etc). Otherwise, supply $uri with the complete URI. + * + * @param string $uri The URI form which a EUri instance is created + * @throws CException When an empty string was supplied for the scheme + * @throws CException When an illegal scheme is supplied + * @throws CException When the scheme is not supported + * @return EUri + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + public static function factory($uri = 'http') + { + // Separate the scheme from the scheme-specific parts + $uri = explode(':', $uri, 2); + $scheme = strtolower($uri[0]); + $schemeSpecific = isset($uri[1]) === true ? $uri[1] : ''; + + if (strlen($scheme) === 0) { + throw new CException('An empty string was supplied for the scheme'); + } + + // Security check: $scheme is used to load a class file, so only alphanumerics are allowed. + if (ctype_alnum($scheme) === false) { + throw new CException('Illegal scheme supplied, only alphanumeric characters are permitted'); + } + + /** + * Create a new EUri object for the $uri. If a subclass of EUri exists for the + * scheme, return an instance of that class. Otherwise, a CException is thrown. + */ + switch ($scheme) { + case 'http': + // Break intentionally omitted + case 'https': + $className = 'EUriHttp'; + break; + + case 'mailto': + // TODO + + default: + + throw new CException("Scheme \"$scheme\" is not supported"); + break; + } + + $schemeHandler = new $className($scheme, $schemeSpecific); + + return $schemeHandler; + } + + /** + * Get the URI's scheme + * + * @return string|false Scheme or false if no scheme is set. + */ + public function getScheme() + { + if (empty($this->_scheme) === false) { + return $this->_scheme; + } else { + return false; + } + } + + /** + * EUri and its subclasses cannot be instantiated directly. + * Use EUri::factory() to return a new EUri object. + * + * @param string $scheme The scheme of the URI + * @param string $schemeSpecific The scheme-specific part of the URI + */ + abstract protected function __construct($scheme, $schemeSpecific = ''); + + /** + * Return a string representation of this URI. + * + * @return string + */ + abstract public function getUri(); + + /** + * Returns TRUE if this URI is valid, or FALSE otherwise. + * + * @return boolean + */ + abstract public function valid(); +} diff --git a/extensions/EHttpClient/EUriHttp.php b/extensions/EHttpClient/EUriHttp.php new file mode 100644 index 0000000..ad09674 --- /dev/null +++ b/extensions/EHttpClient/EUriHttp.php @@ -0,0 +1,690 @@ +_scheme = $scheme; + + // Set up grammar rules for validation via regular expressions. These + // are to be used with slash-delimited regular expression strings. + $this->_regex['alphanum'] = '[^\W_]'; + $this->_regex['escaped'] = '(?:%[\da-fA-F]{2})'; + $this->_regex['mark'] = '[-_.!~*\'()\[\]]'; + $this->_regex['reserved'] = '[;\/?:@&=+$,]'; + $this->_regex['unreserved'] = '(?:' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . ')'; + $this->_regex['segment'] = '(?:(?:' . $this->_regex['unreserved'] . '|' . $this->_regex['escaped'] + . '|[:@&=+$,;])*)'; + $this->_regex['path'] = '(?:\/' . $this->_regex['segment'] . '?)+'; + $this->_regex['uric'] = '(?:' . $this->_regex['reserved'] . '|' . $this->_regex['unreserved'] . '|' + . $this->_regex['escaped'] . ')'; + // If no scheme-specific part was supplied, the user intends to create + // a new URI with this object. No further parsing is required. + if (strlen($schemeSpecific) === 0) { + return; + } + + // Parse the scheme-specific URI parts into the instance variables. + $this->_parseUri($schemeSpecific); + + // Validate the URI + if ($this->valid() === false) { + throw new CException('Invalid URI supplied'); + } + } + + /** + * Creates a EUriHttp from the given string + * + * @param string $uri String to create URI from, must start with + * 'http://' or 'https://' + * @throws InvalidArgumentException When the given $uri is not a string or + * does not start with http:// or https:// + * @throws CException When the given $uri is invalid + * @return EUriHttp + */ + public static function fromString($uri) + { + if (is_string($uri) === false) { + throw new InvalidArgumentException('$uri is not a string'); + } + + $uri = explode(':', $uri, 2); + $scheme = strtolower($uri[0]); + $schemeSpecific = isset($uri[1]) === true ? $uri[1] : ''; + + if (in_array($scheme, array('http', 'https')) === false) { + throw new CException("Invalid scheme: '$scheme'"); + } + + $schemeHandler = new EUriHttp($scheme, $schemeSpecific); + return $schemeHandler; + } + + /** + * Parse the scheme-specific portion of the URI and place its parts into instance variables. + * + * @param string $schemeSpecific The scheme-specific portion to parse + * @throws CException When scheme-specific decoposition fails + * @throws CException When authority decomposition fails + * @return void + */ + protected function _parseUri($schemeSpecific) + { + // High-level decomposition parser + $pattern = '~^((//)([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?$~'; + $status = @preg_match($pattern, $schemeSpecific, $matches); + if ($status === false) { + throw new EUri_Exception('Internal error: scheme-specific decomposition failed'); + } + + // Failed decomposition; no further processing needed + if ($status === false) { + return; + } + + // Save URI components that need no further decomposition + $this->_path = isset($matches[4]) === true ? $matches[4] : ''; + $this->_query = isset($matches[6]) === true ? $matches[6] : ''; + $this->_fragment = isset($matches[8]) === true ? $matches[8] : ''; + + // Additional decomposition to get username, password, host, and port + $combo = isset($matches[3]) === true ? $matches[3] : ''; + $pattern = '~^(([^:@]*)(:([^@]*))?@)?([^:]+)(:(.*))?$~'; + $status = @preg_match($pattern, $combo, $matches); + if ($status === false) { + + throw new CException('Internal error: authority decomposition failed'); + } + + // Failed decomposition; no further processing needed + if ($status === false) { + return; + } + + // Save remaining URI components + $this->_username = isset($matches[2]) === true ? $matches[2] : ''; + $this->_password = isset($matches[4]) === true ? $matches[4] : ''; + $this->_host = isset($matches[5]) === true ? $matches[5] : ''; + $this->_port = isset($matches[7]) === true ? $matches[7] : ''; + + } + + /** + * Returns a URI based on current values of the instance variables. If any + * part of the URI does not pass validation, then an exception is thrown. + * + * @throws CException When one or more parts of the URI are invalid + * @return string + */ + public function getUri() + { + if ($this->valid() === false) { + throw new CException('One or more parts of the URI are invalid'); + } + + $password = strlen($this->_password) > 0 ? ":$this->_password" : ''; + $auth = strlen($this->_username) > 0 ? "$this->_username$password@" : ''; + $port = strlen($this->_port) > 0 ? ":$this->_port" : ''; + $query = strlen($this->_query) > 0 ? "?$this->_query" : ''; + $fragment = strlen($this->_fragment) > 0 ? "#$this->_fragment" : ''; + + return $this->_scheme + . '://' + . $auth + . $this->_host + . $port + . $this->_path + . $query + . $fragment; + } + + /** + * Validate the current URI from the instance variables. Returns true if and only if all + * parts pass validation. + * + * @return boolean + */ + public function valid() + { + // Return true if and only if all parts of the URI have passed validation + return $this->validateUsername() + and $this->validatePassword() + and $this->validateHost() + and $this->validatePort() + and $this->validatePath() + and $this->validateQuery() + and $this->validateFragment(); + } + + /** + * Returns the username portion of the URL, or FALSE if none. + * + * @return string + */ + public function getUsername() + { + return strlen($this->_username) > 0 ? $this->_username : false; + } + + /** + * Returns true if and only if the username passes validation. If no username is passed, + * then the username contained in the instance variable is used. + * + * @param string $username The HTTP username + * @throws CException When username validation fails + * @return boolean + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + public function validateUsername($username = null) + { + if ($username === null) { + $username = $this->_username; + } + + // If the username is empty, then it is considered valid + if (strlen($username) === 0) { + return true; + } + + // Check the username against the allowed values + $status = @preg_match('/^(' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . '|' + . $this->_regex['escaped'] . '|[;:&=+$,])+$/', $username); + if ($status === false) { + throw new CException('Internal error: username validation failed'); + } + + return $status === 1; + } + + /** + * Sets the username for the current URI, and returns the old username + * + * @param string $username The HTTP username + * @throws CException When $username is not a valid HTTP username + * @return string + */ + public function setUsername($username) + { + if ($this->validateUsername($username) === false) { + throw new CException("Username \"$username\" is not a valid HTTP username"); + } + + $oldUsername = $this->_username; + $this->_username = $username; + + return $oldUsername; + } + + /** + * Returns the password portion of the URL, or FALSE if none. + * + * @return string + */ + public function getPassword() + { + return strlen($this->_password) > 0 ? $this->_password : false; + } + + /** + * Returns true if and only if the password passes validation. If no password is passed, + * then the password contained in the instance variable is used. + * + * @param string $password The HTTP password + * @throws CException When password validation fails + * @return boolean + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + public function validatePassword($password = null) + { + if ($password === null) { + $password = $this->_password; + } + + // If the password is empty, then it is considered valid + if (strlen($password) === 0) { + return true; + } + + // If the password is nonempty, but there is no username, then it is considered invalid + if (strlen($password) > 0 and strlen($this->_username) === 0) { + return false; + } + + // Check the password against the allowed values + $status = @preg_match('/^(' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . '|' + . $this->_regex['escaped'] . '|[;:&=+$,])+$/', $password); + if ($status === false) { + throw new CException('Internal error: password validation failed.'); + } + + return $status == 1; + } + + /** + * Sets the password for the current URI, and returns the old password + * + * @param string $password The HTTP password + * @throws CException When $password is not a valid HTTP password + * @return string + */ + public function setPassword($password) + { + if ($this->validatePassword($password) === false) { + throw new CException("Password \"$password\" is not a valid HTTP password."); + } + + $oldPassword = $this->_password; + $this->_password = $password; + + return $oldPassword; + } + + /** + * Returns the domain or host IP portion of the URL, or FALSE if none. + * + * @return string + */ + public function getHost() + { + return strlen($this->_host) > 0 ? $this->_host : false; + } + + /** + * Returns true if and only if the host string passes validation. If no host is passed, + * then the host contained in the instance variable is used. + * + * @param string $host The HTTP host + * @return boolean + * @uses EHostnameValidator + */ + public function validateHost($host = null) + { + if ($host === null) { + $host = $this->_host; + } + + // If the host is empty, then it is considered invalid + if (strlen($host) === 0) { + return false; + } + + + // Check the host against the allowed values; delegated to Zend_Filter. + $validate = new EHostnameValidator(EHostnameValidator::ALLOW_ALL); + + return $validate->isValid($host); + } + + /** + * Sets the host for the current URI, and returns the old host + * + * @param string $host The HTTP host + * @throws CException When $host is nota valid HTTP host + * @return string + */ + public function setHost($host) + { + if ($this->validateHost($host) === false) { + + throw new CException("Host \"$host\" is not a valid HTTP host"); + } + + $oldHost = $this->_host; + $this->_host = $host; + + return $oldHost; + } + + /** + * Returns the TCP port, or FALSE if none. + * + * @return string + */ + public function getPort() + { + return strlen($this->_port) > 0 ? $this->_port : false; + } + + /** + * Returns true if and only if the TCP port string passes validation. If no port is passed, + * then the port contained in the instance variable is used. + * + * @param string $port The HTTP port + * @return boolean + */ + public function validatePort($port = null) + { + if ($port === null) { + $port = $this->_port; + } + + // If the port is empty, then it is considered valid + if (strlen($port) === 0) { + return true; + } + + // Check the port against the allowed values + return ctype_digit((string) $port) and 1 <= $port and $port <= 65535; + } + + /** + * Sets the port for the current URI, and returns the old port + * + * @param string $port The HTTP port + * @throws CException When $port is not a valid HTTP port + * @return string + */ + public function setPort($port) + { + if ($this->validatePort($port) === false) { + throw new CException("Port \"$port\" is not a valid HTTP port."); + } + + $oldPort = $this->_port; + $this->_port = $port; + + return $oldPort; + } + + /** + * Returns the path and filename portion of the URL, or FALSE if none. + * + * @return string + */ + public function getPath() + { + return strlen($this->_path) > 0 ? $this->_path : '/'; + } + + /** + * Returns true if and only if the path string passes validation. If no path is passed, + * then the path contained in the instance variable is used. + * + * @param string $path The HTTP path + * @throws CException When path validation fails + * @return boolean + */ + public function validatePath($path = null) + { + if ($path === null) { + $path = $this->_path; + } + + // If the path is empty, then it is considered valid + if (strlen($path) === 0) { + return true; + } + + // Determine whether the path is well-formed + $pattern = '/^' . $this->_regex['path'] . '$/'; + $status = @preg_match($pattern, $path); + if ($status === false) { + throw new CException('Internal error: path validation failed'); + } + + return (boolean) $status; + } + + /** + * Sets the path for the current URI, and returns the old path + * + * @param string $path The HTTP path + * @throws CException When $path is not a valid HTTP path + * @return string + */ + public function setPath($path) + { + if ($this->validatePath($path) === false) { + throw new CException("Path \"$path\" is not a valid HTTP path"); + } + + $oldPath = $this->_path; + $this->_path = $path; + + return $oldPath; + } + + /** + * Returns the query portion of the URL (after ?), or FALSE if none. + * + * @return string + */ + public function getQuery() + { + return strlen($this->_query) > 0 ? $this->_query : false; + } + + /** + * Returns true if and only if the query string passes validation. If no query is passed, + * then the query string contained in the instance variable is used. + * + * @param string $query The query to validate + * @throws CException When query validation fails + * @return boolean + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + public function validateQuery($query = null) + { + if ($query === null) { + $query = $this->_query; + } + + // If query is empty, it is considered to be valid + if (strlen($query) === 0) { + return true; + } + + // Determine whether the query is well-formed + $pattern = '/^' . $this->_regex['uric'] . '*$/'; + $status = @preg_match($pattern, $query); + if ($status === false) { + throw new CException('Internal error: query validation failed'); + } + + return $status == 1; + } + + /** + * Set the query string for the current URI, and return the old query + * string This method accepts both strings and arrays. + * + * @param string|array $query The query string or array + * @throws CException When $query is not a valid query string + * @return string Old query string + */ + public function setQuery($query) + { + $oldQuery = $this->_query; + + // If query is empty, set an empty string + if (empty($query) === true) { + $this->_query = ''; + return $oldQuery; + } + + // If query is an array, make a string out of it + if (is_array($query) === true) { + $query = http_build_query($query, '', '&'); + } else { + // If it is a string, make sure it is valid. If not parse and encode it + $query = (string) $query; + if ($this->validateQuery($query) === false) { + parse_str($query, $queryArray); + $query = http_build_query($queryArray, '', '&'); + } + } + + // Make sure the query is valid, and set it + if ($this->validateQuery($query) === false) { + throw new CException("'$query' is not a valid query string"); + } + + $this->_query = $query; + + return $oldQuery; + } + + /** + * Returns the fragment portion of the URL (after #), or FALSE if none. + * + * @return string|false + */ + public function getFragment() + { + return strlen($this->_fragment) > 0 ? $this->_fragment : false; + } + + /** + * Returns true if and only if the fragment passes validation. If no fragment is passed, + * then the fragment contained in the instance variable is used. + * + * @param string $fragment Fragment of an URI + * @throws CException When fragment validation fails + * @return boolean + * @link http://www.faqs.org/rfcs/rfc2396.html + */ + public function validateFragment($fragment = null) + { + if ($fragment === null) { + $fragment = $this->_fragment; + } + + // If fragment is empty, it is considered to be valid + if (strlen($fragment) === 0) { + return true; + } + + // Determine whether the fragment is well-formed + $pattern = '/^' . $this->_regex['uric'] . '*$/'; + $status = @preg_match($pattern, $fragment); + if ($status === false) { + throw new CException('Internal error: fragment validation failed'); + } + + return (boolean) $status; + } + + /** + * Sets the fragment for the current URI, and returns the old fragment + * + * @param string $fragment Fragment of the current URI + * @throws CException When $fragment is not a valid HTTP fragment + * @return string + */ + public function setFragment($fragment) + { + if ($this->validateFragment($fragment) === false) { + throw new CException("Fragment \"$fragment\" is not a valid HTTP fragment"); + } + + $oldFragment = $this->_fragment; + $this->_fragment = $fragment; + + return $oldFragment; + } +} diff --git a/extensions/EHttpClient/adapter/EHttpClientAdapterCurl.php b/extensions/EHttpClient/adapter/EHttpClientAdapterCurl.php new file mode 100644 index 0000000..59cb651 --- /dev/null +++ b/extensions/EHttpClient/adapter/EHttpClientAdapterCurl.php @@ -0,0 +1,492 @@ +setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']); + unset($config['proxy_user'], $config['proxy_pass']); + } + + foreach ($config as $k => $v) { + $option = strtolower($k); + switch($option) { + case 'proxy_host': + $this->setCurlOption(CURLOPT_PROXY, $v); + break; + case 'proxy_port': + $this->setCurlOption(CURLOPT_PROXYPORT, $v); + break; + default: + $this->_config[$option] = $v; + break; + } + } + + return $this; + } + + /** + * Retrieve the array of all configuration options + * + * @return array + */ + public function getConfig() + { + return $this->_config; + } + + /** + * Direct setter for cURL adapter related options. + * + * @param string|int $option + * @param mixed $value + * @return EHttpAdapterCurl + */ + public function setCurlOption($option, $value) + { + if (!isset($this->_config['curloptions'])) { + $this->_config['curloptions'] = array(); + } + $this->_config['curloptions'][$option] = $value; + return $this; + } + + /** + * Initialize curl + * + * @param string $host + * @param int $port + * @param boolean $secure + * @return void + * @throws EHttpClientAdapterException if unable to connect + */ + public function connect($host, $port = 80, $secure = false) + { + // If we're already connected, disconnect first + if ($this->_curl) { + $this->close(); + } + + // If we are connected to a different server or port, disconnect first + if ($this->_curl + && is_array($this->_connected_to) + && ($this->_connected_to[0] != $host + || $this->_connected_to[1] != $port) + ) { + $this->close(); + } + + // Do the actual connection + $this->_curl = curl_init(); + if ($port != 80) { + curl_setopt($this->_curl, CURLOPT_PORT, intval($port)); + } + + // Set timeout + curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']); + + // Set Max redirects + curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']); + + if (!$this->_curl) { + $this->close(); + throw new EHttpClientException( + Yii::t('EHttpClient','Unable to Connect to ' . $host . ':' . $port)); + } + + if ($secure !== false) { + // Behave the same like Zend_Http_Adapter_Socket on SSL options. + if (isset($this->_config['sslcert'])) { + curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']); + } + if (isset($this->_config['sslpassphrase'])) { + curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']); + } + } + + // Update connected_to + $this->_connected_to = array($host, $port); + } + + /** + * Send request to the remote server + * + * @param string $method + * @param EUriHttp $uri + * @param float $http_ver + * @param array $headers + * @param string $body + * @return string $request + * @throws EHttpClientAdapterException If connection fails, connected to wrong host, no PUT file defined, unsupported method, or unsupported cURL option + */ + public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '') + { + // Make sure we're properly connected + if (!$this->_curl) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Trying to write but we are not connected")); + } + + if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Trying to write but we are connected to the wrong host")); + } + + // set URL + curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString()); + + // ensure correct curl call + $curlValue = true; + switch ($method) { + case EHttpClient::GET: + $curlMethod = CURLOPT_HTTPGET; + break; + + case EHttpClient::POST: + $curlMethod = CURLOPT_POST; + break; + + case EHttpClient::PUT: + // There are two different types of PUT request, either a Raw Data string has been set + // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used. + if(is_resource($body)) { + $this->_config['curloptions'][CURLOPT_INFILE] = $body; + } + if (isset($this->_config['curloptions'][CURLOPT_INFILE])) { + // Now we will probably already have Content-Length set, so that we have to delete it + // from $headers at this point: + foreach ($headers AS $k => $header) { + if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) { + if(is_resource($body)) { + $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1]; + } + unset($headers[$k]); + } + } + + if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Cannot set a file-handle for cURL option CURLOPT_INFILE without also setting its size in CURLOPT_INFILESIZE.")); + } + + if(is_resource($body)) { + $body = ''; + } + + $curlMethod = CURLOPT_PUT; + } else { + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "PUT"; + } + break; + + case EHttpClient::DELETE: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "DELETE"; + break; + + case EHttpClient::OPTIONS: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "OPTIONS"; + break; + + case EHttpClient::TRACE: + $curlMethod = CURLOPT_CUSTOMREQUEST; + $curlValue = "TRACE"; + break; + + default: + // For now, through an exception for unsupported request methods + throw new EHttpClientException( + Yii::t('EHttpClient',"Method currently not supported")); + } + + if(is_resource($body) && $curlMethod != CURLOPT_PUT) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Streaming requests are allowed only with PUT")); + } + + // get http version to use + $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0; + + // mark as HTTP request and set HTTP method + curl_setopt($this->_curl, $curlHttp, true); + curl_setopt($this->_curl, $curlMethod, $curlValue); + + if($this->out_stream) { + // headers will be read into the response + curl_setopt($this->_curl, CURLOPT_HEADER, false); + curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader")); + // and data will be written into the file + curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream); + } else { + // ensure headers are also returned + curl_setopt($this->_curl, CURLOPT_HEADER, true); + + // ensure actual response is returned + curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true); + } + + // set additional headers + $headers['Accept'] = ''; + curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers); + + /** + * Make sure POSTFIELDS is set after $curlMethod is set: + * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161 + */ + if ($method == EHttpClient::POST) { + curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); + } elseif ($curlMethod == CURLOPT_PUT) { + // this covers a PUT by file-handle: + // Make the setting of this options explicit (rather than setting it through the loop following a bit lower) + // to group common functionality together. + curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]); + curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]); + unset($this->_config['curloptions'][CURLOPT_INFILE]); + unset($this->_config['curloptions'][CURLOPT_INFILESIZE]); + } elseif ($method == EHttpClient::PUT) { + // This is a PUT by a setRawData string, not by file-handle + curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body); + } + + // set additional curl options + if (isset($this->_config['curloptions'])) { + foreach ((array)$this->_config['curloptions'] as $k => $v) { + if (!in_array($k, $this->_invalidOverwritableCurlOptions)) { + if (curl_setopt($this->_curl, $k, $v) == false) { + throw new EHttpClientException( + Yii::t('EHttpClient',sprintf("Unknown or erroreous cURL option '%s' set", $k))); + } + } + } + } + + // send the request + $response = curl_exec($this->_curl); + + // if we used streaming, headers are already there + if(!is_resource($this->out_stream)) { + $this->_response = $response; + } + + $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT); + $request .= $body; + + if (empty($this->_response)) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Error in cURL request: " . curl_error($this->_curl))); + } + + // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to do it again + if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) { + $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response); + } + + // Eliminate multiple HTTP responses. + do { + $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2); + $again = false; + + if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) { + $this->_response = $parts[1]; + $again = true; + } + } while ($again); + + // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string: + if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) { + $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response); + } + + return $request; + } + + /** + * Return read response from server + * + * @return string + */ + public function read() + { + return $this->_response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if(is_resource($this->_curl)) { + curl_close($this->_curl); + } + $this->_curl = null; + $this->_connected_to = array(null, null); + } + + /** + * Get cUrl Handle + * + * @return resource + */ + public function getHandle() + { + return $this->_curl; + } + + /** + * Set output stream for the response + * + * @param resource $stream + * @return EHttpClientAdapterSocket + */ + public function setOutputStream($stream) + { + $this->out_stream = $stream; + return $this; + } + + /** + * Header reader function for CURL + * + * @param resource $curl + * @param string $header + * @return int + */ + public function readHeader($curl, $header) + { + $this->_response .= $header; + return strlen($header); + } +} diff --git a/extensions/EHttpClient/adapter/EHttpClientAdapterInterface.php b/extensions/EHttpClient/adapter/EHttpClientAdapterInterface.php new file mode 100644 index 0000000..adc88df --- /dev/null +++ b/extensions/EHttpClient/adapter/EHttpClientAdapterInterface.php @@ -0,0 +1,87 @@ + false, + 'ssltransport' => 'ssl', + 'sslcert' => null, + 'sslpassphrase' => null + ); + + /** + * Request method - will be set by write() and might be used by read() + * + * @var string + */ + protected $method = null; + + /** + * Adapter constructor, currently empty. Config is set using setConfig() + * + */ + public function __construct() + { + } + + /** + * Set the configuration array for the adapter + * + * @param array $config + */ + public function setConfig($config = array()) + { + if (! is_array($config)) { + + throw new EHttpClientException( + Yii::t('EHttpClient', + '$config expects an array, ' . gettype($config) . ' received.')); + } + + foreach ($config as $k => $v) { + $this->config[strtolower($k)] = $v; + } + } + + /** + * Connect to the remote server + * + * @param string $host + * @param int $port + * @param boolean $secure + * @param int $timeout + */ + public function connect($host, $port = 80, $secure = false) + { + // If the URI should be accessed via SSL, prepend the Hostname with ssl:// + $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; + + // If we are connected to the wrong host, disconnect first + if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { + if (is_resource($this->socket)) $this->close(); + } + + // Now, if we are not connected, connect + if (! is_resource($this->socket) || ! $this->config['keepalive']) { + $context = stream_context_create(); + if ($secure) { + if ($this->config['sslcert'] !== null) { + if (! stream_context_set_option($context, 'ssl', 'local_cert', + $this->config['sslcert'])) { + throw new EHttpClientException( + Yii::t('EHttpClient','Unable to set sslcert option')); + } + } + if ($this->config['sslpassphrase'] !== null) { + if (! stream_context_set_option($context, 'ssl', 'passphrase', + $this->config['sslpassphrase'])) { + throw new EHttpClientException( + Yii::t('EHttpClient','Unable to set sslpassphrase option')); + } + } + } + + $flags = STREAM_CLIENT_CONNECT; + if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT; + + $this->socket = @stream_socket_client($host . ':' . $port, + $errno, + $errstr, + (int) $this->config['timeout'], + $flags, + $context); + if (! $this->socket) { + $this->close(); + throw new EHttpClientException( + Yii::t('EHttpClient', + 'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr)); + } + + // Set the stream timeout + if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) { + throw new EHttpClientException( + Yii::t('EHttpClient','Unable to set the connection timeout')); + } + + // Update connected_to + $this->connected_to = array($host, $port); + } + } + + /** + * Send request to the remote server + * + * @param string $method + * @param EUriHttp $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // Make sure we're properly connected + if (! $this->socket) { + throw new EHttpClientException( + Yii::t('EHttpClient','Trying to write but we are not connected')); + } + + $host = $uri->getHost(); + $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; + if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { + throw new EHttpClientException( + Yii::t('EHttpClient','Trying to write but we are connected to the wrong host')); + } + + // Save request method for later + $this->method = $method; + + // Build request headers + $path = $uri->getPath(); + if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); + $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; + foreach ($headers as $k => $v) { + if (is_string($k)) $v = ucfirst($k) . ": $v"; + $request .= "$v\r\n"; + } + + // Add the request body + $request .= "\r\n" . $body; + + // Send the request + if (! @fwrite($this->socket, $request)) { + throw new EHttpClientException( + Yii::t('EHttpClient','Error writing request to server')); + } + + return $request; + } + + /** + * Read response from server + * + * @return string + */ + public function read() + { + // First, read headers only + $response = ''; + $gotStatus = false; + while ($line = @fgets($this->socket)) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (!chop($line)) break; + } + } + + $statusCode = EHttpResponse::extractCode($response); + + // Handle 100 and 101 responses internally by restarting the read again + if ($statusCode == 100 || $statusCode == 101) return $this->read(); + + /** + * Responses to HEAD requests and 204 or 304 responses are not expected + * to have a body - stop reading here + */ + if ($statusCode == 304 || $statusCode == 204 || + $this->method == EHttpClient::HEAD) return $response; + + // Check headers to see what kind of connection / transfer encoding we have + $headers = EHttpResponse::extractHeaders($response); + + // if the connection is set to close, just read until socket closes + if (isset($headers['connection']) && $headers['connection'] == 'close') { + while ($buff = @fread($this->socket, 8192)) { + $response .= $buff; + } + + $this->close(); + + // Else, if we got a transfer-encoding header (chunked body) + } elseif (isset($headers['transfer-encoding'])) { + if ($headers['transfer-encoding'] == 'chunked') { + do { + $chunk = ''; + $line = @fgets($this->socket); + $chunk .= $line; + + $hexchunksize = ltrim(chop($line), '0'); + $hexchunksize = strlen($hexchunksize) ? strtolower($hexchunksize) : 0; + + $chunksize = hexdec(chop($line)); + if (dechex($chunksize) != $hexchunksize) { + @fclose($this->socket); + + throw new EHttpClientException( + Yii::t('EHttpClient','Invalid chunk size "' . + $hexchunksize . '" unable to read chunked body')); + } + + $left_to_read = $chunksize; + while ($left_to_read > 0) { + $line = @fread($this->socket, $left_to_read); + $chunk .= $line; + $left_to_read -= strlen($line); + } + + $chunk .= @fgets($this->socket); + $response .= $chunk; + } while ($chunksize > 0); + } else { + throw new EHttpClientException( + Yii::t('EHttpClient','Cannot handle "' . + $headers['transfer-encoding'] . '" transfer encoding')); + } + + // Else, if we got the content-length header, read this number of bytes + } elseif (isset($headers['content-length'])) { + $left_to_read = $headers['content-length']; + $chunk = ''; + while ($left_to_read > 0) { + $chunk = @fread($this->socket, $left_to_read); + $left_to_read -= strlen($chunk); + $response .= $chunk; + } + + // Fallback: just read the response (should not happen) + } else { + while ($buff = @fread($this->socket, 8192)) { + $response .= $buff; + } + + $this->close(); + } + + return $response; + } + + /** + * Close the connection to the server + * + */ + public function close() + { + if (is_resource($this->socket)) @fclose($this->socket); + $this->socket = null; + $this->connected_to = array(null, null); + } + + /** + * Destructor: make sure the socket is disconnected + * + * If we are in persistent TCP mode, will not close the connection + * + */ + public function __destruct() + { + if (! $this->config['persistent']) { + if ($this->socket) $this->close(); + } + } +} diff --git a/extensions/EHttpClient/adapter/EHttpClientAdapterStream.php b/extensions/EHttpClient/adapter/EHttpClientAdapterStream.php new file mode 100755 index 0000000..1654eae --- /dev/null +++ b/extensions/EHttpClient/adapter/EHttpClientAdapterStream.php @@ -0,0 +1,56 @@ + 'ssl', + 'proxy_host' => '', + 'proxy_port' => 8080, + 'proxy_user' => '', + 'proxy_pass' => '', + 'proxy_auth' => EHttpClient::AUTH_BASIC, + 'persistent' => false + ); + + /** + * Whether HTTPS CONNECT was already negotiated with the proxy or not + * + * @var boolean + */ + protected $negotiated = false; + + /** + * Connect to the remote server + * + * Will try to connect to the proxy server. If no proxy was set, will + * fall back to the target server (behave like regular Socket adapter) + * + * @param string $host + * @param int $port + * @param boolean $secure + * @param int $timeout + */ + public function connect($host, $port = 80, $secure = false) + { + // If no proxy is set, fall back to Socket adapter + if (! $this->config['proxy_host']) return parent::connect($host, $port, $secure); + + // Go through a proxy - the connection is actually to the proxy server + $host = $this->config['proxy_host']; + $port = $this->config['proxy_port']; + + // If we are connected to the wrong proxy, disconnect first + if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { + if (is_resource($this->socket)) $this->close(); + } + + // Now, if we are not connected, connect + if (! is_resource($this->socket) || ! $this->config['keepalive']) { + $this->socket = @fsockopen($host, $port, $errno, $errstr, (int) $this->config['timeout']); + if (! $this->socket) { + $this->close(); + + throw new EHttpClientException( + Yii::t('EHttpClient', + 'Unable to Connect to proxy server ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr)); + } + + // Set the stream timeout + if (!stream_set_timeout($this->socket, (int) $this->config['timeout'])) { + throw new EHttpClientException( + Yii::t('EHttpClient', + 'Unable to set the connection timeout')); + } + + // Update connected_to + $this->connected_to = array($host, $port); + } + } + + /** + * Send request to the proxy server + * + * @param string $method + * @param EUriHttp $uri + * @param string $http_ver + * @param array $headers + * @param string $body + * @return string Request as string + */ + public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') + { + // If no proxy is set, fall back to default Socket adapter + if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body); + + // Make sure we're properly connected + if (! $this->socket) { + throw new EHttpClientException( + Yii::t('EHttpClient', + "Trying to write but we are not connected")); + } + + $host = $this->config['proxy_host']; + $port = $this->config['proxy_port']; + + if ($this->connected_to[0] != $host || $this->connected_to[1] != $port) { + + throw new EHttpClientException( + Yii::t('EHttpClient',"Trying to write but we are connected to the wrong proxy server")); + } + + // Add Proxy-Authorization header + if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) + $headers['proxy-authorization'] = EHttpClient::encodeAuthHeader( + $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] + ); + + // if we are proxying HTTPS, preform CONNECT handshake with the proxy + if ($uri->getScheme() == 'https' && (! $this->negotiated)) { + $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers); + $this->negotiated = true; + } + + // Save request method for later + $this->method = $method; + + // Build request headers + $request = "{$method} {$uri->__toString()} HTTP/{$http_ver}\r\n"; + + // Add all headers to the request string + foreach ($headers as $k => $v) { + if (is_string($k)) $v = "$k: $v"; + $request .= "$v\r\n"; + } + + // Add the request body + $request .= "\r\n" . $body; + + // Send the request + if (! @fwrite($this->socket, $request)) { + + throw new EHttpClientException( + Yii::t('EHttpClient',"Error writing request to proxy server")); + } + + return $request; + } + + /** + * Preform handshaking with HTTPS proxy using CONNECT method + * + * @param string $host + * @param integer $port + * @param string $http_ver + * @param array $headers + */ + protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array()) + { + $request = "CONNECT $host:$port HTTP/$http_ver\r\n" . + "Host: " . $this->config['proxy_host'] . "\r\n"; + + // Add the user-agent header + if (isset($this->config['useragent'])) { + $request .= "User-agent: " . $this->config['useragent'] . "\r\n"; + } + + // If the proxy-authorization header is set, send it to proxy but remove + // it from headers sent to target host + if (isset($headers['proxy-authorization'])) { + $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n"; + unset($headers['proxy-authorization']); + } + + $request .= "\r\n"; + + // Send the request + if (! @fwrite($this->socket, $request)) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Error writing request to proxy server")); + } + + // Read response headers only + $response = ''; + $gotStatus = false; + while ($line = @fgets($this->socket)) { + $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); + if ($gotStatus) { + $response .= $line; + if (!chop($line)) break; + } + } + + // Check that the response from the proxy is 200 + if (EHttpResponse::extractCode($response) != 200) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Unable to connect to HTTPS proxy. Server response: " . $response)); + } + + // If all is good, switch socket to secure mode. We have to fall back + // through the different modes + $modes = array( + STREAM_CRYPTO_METHOD_TLS_CLIENT, + STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + STREAM_CRYPTO_METHOD_SSLv2_CLIENT + ); + + $success = false; + foreach($modes as $mode) { + $success = stream_socket_enable_crypto($this->socket, true, $mode); + if ($success) break; + } + + if (! $success) { + throw new EHttpClientException( + Yii::t('EHttpClient',"Unable to connect to" . + " HTTPS server through proxy: could not negotiate secure connection.")); + } + } + + /** + * Close the connection to the server + * + */ + public function close() + { + parent::close(); + $this->negotiated = false; + } + + /** + * Destructor: make sure the socket is disconnected + * + */ + public function __destruct() + { + if ($this->socket) $this->close(); + } +} diff --git a/extensions/EWebBrowser/EWebBrowser.php b/extensions/EWebBrowser/EWebBrowser.php new file mode 100755 index 0000000..ff2cb49 --- /dev/null +++ b/extensions/EWebBrowser/EWebBrowser.php @@ -0,0 +1,1111 @@ +attributes = new CAttributeCollection(); + $this->attributes->add('agent', ''); + $this->attributes->add('browser_name', ''); + $this->attributes->add('version', ''); + $this->attributes->add('platform', ''); + $this->attributes->add('os', ''); + $this->attributes->add('is_aol', false); + $this->attributes->add('is_mobile', false); + $this->attributes->add('is_robot', false); + $this->attributes->add('aol_version', ''); + + if(null !== $eventHandler) + $this->onDetection = $eventHandler; + + if (null !== $useragent) + $this->setUserAgent($useragent0); + else + { + $this->reset(); + $this->determine(); + } + } + /** + * Event raised when user agent has been parsed for browser discovery + * @param $event + */ + public function onDetection($event) + { + $this->raiseEvent('onDetection', $event); + } + + /** + * Reset all properties + */ + public function reset() + { + $this->attributes->agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $this->attributes->browser_name = self::BROWSER_UNKNOWN; + $this->attributes->version = self::VERSION_UNKNOWN; + $this->attributes->platform = self::PLATFORM_UNKNOWN; + $this->attributes->os = self::OPERATING_SYSTEM_UNKNOWN; + $this->attributes->is_aol = false; + $this->attributes->is_mobile = false; + $this->attributes->is_robot = false; + $this->attributes->aol_version = self::VERSION_UNKNOWN; + } + + /** + * Check to see if the specific browser is valid + * @param string $browserName + * @return True if the browser is the specified browser + */ + function isBrowser($browserName) + { + return( 0 == strcasecmp($this->attributes->browser_name, trim($browserName))); + } + + /** + * The name of the browser. All return types are from the class contants + * @return string Name of the browser + */ + public function getBrowser() + { + return $this->attributes->browser_name; + } + + /** + * Set the name of the browser + * @param $browser The name of the Browser + */ + public function setBrowser($browser) + { + return $this->attributes->browser_name = $browser; + } + + /** + * The name of the platform. All return types are from the class contants + * @return string Name of the browser + */ + public function getPlatform() + { + return $this->attributes->platform; + } + + /** + * Set the name of the platform + * @param $platform The name of the Platform + */ + public function setPlatform($platform) + { + return $this->attributes->platform = $platform; + } + + /** + * The version of the browser. + * @return string Version of the browser (will only contain alpha-numeric characters and a period) + */ + public function getVersion() + { + return $this->attributes->version; + } + + /** + * Set the version of the browser + * @param $version The version of the Browser + */ + public function setVersion($version) + { + $this->attributes->version = preg_replace('/[^0-9,.,a-z,A-Z-]/', '', $version); + } + + /** + * The version of AOL. + * @return string Version of AOL (will only contain alpha-numeric characters and a period) + */ + public function getAolVersion() + { + return $this->attributes->aol_version; + } + + /** + * Set the version of AOL + * @param $version The version of AOL + */ + public function setAolVersion($version) + { + $this->attributes->aol_version = preg_replace('/[^0-9,.,a-z,A-Z]/', '', $version); + } + + /** + * Is the browser from AOL? + * @return boolean True if the browser is from AOL otherwise false + */ + public function getIsAol() + { + return $this->attributes->is_aol; + } + /** + * Set the browser to be from AOL + * @param $isAol + */ + protected function setIsAol($isAol) + { + $this->attributes->is_aol = $isAol; + } + + /** + * Is the browser from a mobile device? + * @return boolean True if the browser is from a mobile device otherwise false + */ + public function getIsMobile() + { + return $this->attributes->is_mobile; + } + /** + * Set the Browser to be mobile + * @param boolean $value is the browser a mobile brower or not + */ + protected function setMobile($value) + { + $this->attributes->is_mobile = $value; + } + /** + * Is the browser from a robot (ex Slurp,GoogleBot)? + * @return boolean True if the browser is from a robot otherwise false + */ + public function getIsRobot() + { + return $this->attributes->is_robot; + } + /** + * Set the Browser to be a robot + * @param boolean $value is the browser a robot or not + */ + protected function setRobot($value) + { + $this->attributes->is_robot = $value; + } + + /** + * Get the user agent value in use to determine the browser + * @return string The user agent from the HTTP header + */ + public function getUserAgent() + { + return $this->attributes->agent; + } + + /** + * Set the user agent value (the construction will use the HTTP header value - this will overwrite it) + * @param $agent_string The value for the User Agent + */ + public function setUserAgent($agent_string) + { + $this->reset(); + $this->attributes->agent = $agent_string; + $this->determine(); + } + + /** + * Used to determine if the browser is actually "chromeframe" + * @since 1.7 + * @return boolean True if the browser is using chromeframe + */ + public function isChromeFrame() + { + return( strpos($this->attributes->agent, "chromeframe") !== false ); + } + + /** + * Magic Method + * Returns a formatted string with a summary of the details of the browser. + * @return string formatted string with a summary of the browser + */ + public function __toString() + { + return "Browser Name:{$this->getBrowser()}
    \n" . + "Browser Version:{$this->getVersion()}
    \n" . + "Browser User Agent String:{$this->getUserAgent()}
    \n" . + "Platform:{$this->getPlatform()}
    "; + } + + /** + * Protected routine to calculate and determine what the browser is in use (including platform) + */ + protected function determine() + { + $this->checkPlatform(); + $this->checkBrowsers(); + $this->checkForAol(); + + $event = new EWebBrowserEvent($this); + + foreach($this->attributes as $key=>$value) + $event->{$key}=$value; + + $this->onDetection($event); + } + + /** + * Protected routine to determine the browser type + * @return boolean True if the browser was detected otherwise false + */ + protected function checkBrowsers() + { + return ( + // well-known, well-used + // Special Notes: + // (1) Opera must be checked before FireFox due to the odd + // user agents used in some older versions of Opera + // (2) WebTV is strapped onto Internet Explorer so we must + // check for WebTV before IE + // (3) (deprecated) Galeon is based on Firefox and needs to be + // tested before Firefox is tested + // (4) OmniWeb is based on Safari so OmniWeb check must occur + // before Safari + // (5) Netscape 9+ is based on Firefox so Netscape checks + // before FireFox are necessary + $this->checkBrowserWebTv() || + $this->checkBrowserInternetExplorer() || + $this->checkBrowserOpera() || + $this->checkBrowserGaleon() || + $this->checkBrowserNetscapeNavigator9Plus() || + $this->checkBrowserFirefox() || + $this->checkBrowserChrome() || + $this->checkBrowserOmniWeb() || + // common mobile + $this->checkBrowserAndroid() || + $this->checkBrowseriPad() || + $this->checkBrowseriPod() || + $this->checkBrowseriPhone() || + $this->checkBrowserBlackBerry() || + $this->checkBrowserNokia() || + // common bots + $this->checkBrowserGoogleBot() || + $this->checkBrowserMSNBot() || + $this->checkBrowserSlurp() || + // WebKit base check (post mobile and others) + $this->checkBrowserSafari() || + // everyone else + $this->checkBrowserNetPositive() || + $this->checkBrowserFirebird() || + $this->checkBrowserKonqueror() || + $this->checkBrowserIcab() || + $this->checkBrowserPhoenix() || + $this->checkBrowserAmaya() || + $this->checkBrowserLynx() || + $this->checkBrowserShiretoko() || + $this->checkBrowserIceCat() || + $this->checkBrowserW3CValidator() || + $this->checkBrowserMozilla() /* Mozilla is such an open standard that you must check it last */ + ); + } + + /** + * Determine if the user is using a BlackBerry (last updated 1.7) + * @return boolean True if the browser is the BlackBerry browser otherwise false + */ + protected function checkBrowserBlackBerry() + { + if (stripos($this->getUserAgent(), 'blackberry') !== false) + { + $aresult = explode("/", stristr($this->getUserAgent(), "BlackBerry")); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_BLACKBERRY); + $this->setMobile(true); + return true; + } + return false; + } + + /** + * Determine if the user is using an AOL User Agent (last updated 1.7) + * @return boolean True if the browser is from AOL otherwise false + */ + protected function checkForAol() + { + $this->setIsAol(false); + $this->setAolVersion(self::VERSION_UNKNOWN); + + if (stripos($this->getUserAgent(), 'aol') !== false) + { + $aversion = explode(' ', stristr($this->getUserAgent(), 'AOL')); + $this->setIsAol(true); + $this->setAolVersion(preg_replace('/[^0-9\.a-z]/i', '', $aversion[1])); + return true; + } + return false; + } + + /** + * Determine if the browser is the GoogleBot or not (last updated 1.7) + * @return boolean True if the browser is the GoogletBot otherwise false + */ + protected function checkBrowserGoogleBot() + { + if (stripos($this->getUserAgent(), 'googlebot') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'googlebot')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion(str_replace(';', '', $aversion[0])); + $this->setBrowser(self::BROWSER_GOOGLEBOT); + $this->setRobot(true); + return true; + } + return false; + } + + /** + * Determine if the browser is the MSNBot or not (last updated 1.9) + * @return boolean True if the browser is the MSNBot otherwise false + */ + protected function checkBrowserMSNBot() + { + if (stripos($this->getUserAgent(), "msnbot") !== false) + { + $aresult = explode("/", stristr($this->getUserAgent(), "msnbot")); + $aversion = explode(" ", $aresult[1]); + $this->setVersion(str_replace(";", "", $aversion[0])); + $this->setBrowser(self::BROWSER_MSNBOT); + $this->setRobot(true); + return true; + } + return false; + } + + /** + * Determine if the browser is the W3C Validator or not (last updated 1.7) + * @return boolean True if the browser is the W3C Validator otherwise false + */ + protected function checkBrowserW3CValidator() + { + if (stripos($this->getUserAgent(), 'W3C-checklink') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'W3C-checklink')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_W3CVALIDATOR); + return true; + } else if (stripos($this->getUserAgent(), 'W3C_Validator') !== false) + { + // Some of the Validator versions do not delineate w/ a slash - add it back in + $ua = str_replace("W3C_Validator ", "W3C_Validator/", $this->getUserAgent()); + $aresult = explode('/', stristr($ua, 'W3C_Validator')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_W3CVALIDATOR); + return true; + } + return false; + } + + /** + * Determine if the browser is the Yahoo! Slurp Robot or not (last updated 1.7) + * @return boolean True if the browser is the Yahoo! Slurp Robot otherwise false + */ + protected function checkBrowserSlurp() + { + if (stripos($this->getUserAgent(), 'slurp') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Slurp')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_SLURP); + $this->setRobot(true); + $this->setMobile(false); + return true; + } + return false; + } + + /** + * Determine if the browser is Internet Explorer or not (last updated 1.7) + * @return boolean True if the browser is Internet Explorer otherwise false + */ + protected function checkBrowserInternetExplorer() + { + $agent = $this->getUserAgent(); + // Test for v1 - v1.5 IE + if (stripos($agent, 'microsoft internet explorer') !== false) + { + $this->setBrowser(self::BROWSER_IE); + $this->setVersion('1.0'); + $aresult = stristr($this->getUserAgent(), '/'); + if (preg_match('/308|425|426|474|0b1/i', $aresult)) + { + $this->setVersion('1.5'); + } + return true; + } + // Test for versions > 1.5 + else if (stripos($agent, 'msie') !== false && stripos($agent, 'opera') === false) + { + // See if the browser is the odd MSN Explorer + if (stripos($agent, 'msnb') !== false) + { + $aresult = explode(' ', stristr(str_replace(';', '; ', $agent), 'MSN')); + $this->setBrowser(self::BROWSER_MSN); + $this->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1])); + return true; + } + $aresult = explode(' ', stristr(str_replace(';', '; ', $agent), 'msie')); + $this->setBrowser(self::BROWSER_IE); + $this->setVersion(str_replace(array('(', ')', ';'), '', $aresult[1])); + return true; + } + // Test for Pocket IE + else if (stripos($agent, 'mspie') !== false || stripos($agent, 'pocket') !== false) + { + $aresult = explode(' ', stristr($agent, 'mspie')); + $this->setPlatform(self::PLATFORM_WINDOWS_CE); + $this->setBrowser(self::BROWSER_POCKET_IE); + $this->setMobile(true); + + if (stripos($agent, 'mspie') !== false) + { + $this->setVersion($aresult[1]); + } else + { + $aversion = explode('/', $agent); + $this->setVersion($aversion[1]); + } + return true; + } + return false; + } + + /** + * Determine if the browser is Opera or not (last updated 1.7) + * @return boolean True if the browser is Opera otherwise false + */ + protected function checkBrowserOpera() + { + $agent = $this->getUserAgent(); + if (stripos($agent, 'opera mini') !== false) + { + $resultant = stristr($agent, 'opera mini'); + if (preg_match('/\//', $resultant)) + { + $aresult = explode('/', $resultant); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $aversion = explode(' ', stristr($resultant, 'opera mini')); + $this->setVersion($aversion[1]); + } + $this->setBrowser(self::BROWSER_OPERA_MINI); + $this->setMobile(true); + return true; + } else if (stripos($agent, 'opera') !== false) + { + $resultant = stristr($agent, 'opera'); + if (preg_match('/Version\/(10.*)$/', $resultant, $matches)) + { + $this->setVersion($matches[1]); + } else if (preg_match('/\//', $resultant)) + { + $aresult = explode('/', str_replace("(", " ", $resultant)); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $aversion = explode(' ', stristr($resultant, 'opera')); + $this->setVersion(isset($aversion[1]) ? $aversion[1] : ""); + } + $this->setBrowser(self::BROWSER_OPERA); + return true; + } + return false; + } + + /** + * Determine if the browser is Chrome or not (last updated 1.7) + * @return boolean True if the browser is Chrome otherwise false + */ + protected function checkBrowserChrome() + { + if (stripos($this->getUserAgent(), 'Chrome') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Chrome')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_CHROME); + return true; + } + return false; + } + + /** + * Determine if the browser is WebTv or not (last updated 1.7) + * @return boolean True if the browser is WebTv otherwise false + */ + protected function checkBrowserWebTv() + { + if (stripos($this->getUserAgent(), 'webtv') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'webtv')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_WEBTV); + return true; + } + return false; + } + + /** + * Determine if the browser is NetPositive or not (last updated 1.7) + * @return boolean True if the browser is NetPositive otherwise false + */ + protected function checkBrowserNetPositive() + { + if (stripos($this->getUserAgent(), 'NetPositive') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'NetPositive')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion(str_replace(array('(', ')', ';'), '', $aversion[0])); + $this->setBrowser(self::BROWSER_NETPOSITIVE); + return true; + } + return false; + } + + /** + * Determine if the browser is Galeon or not (last updated 1.7) + * @return boolean True if the browser is Galeon otherwise false + */ + protected function checkBrowserGaleon() + { + if (stripos($this->getUserAgent(), 'galeon') !== false) + { + $aresult = explode(' ', stristr($this->getUserAgent(), 'galeon')); + $aversion = explode('/', $aresult[0]); + $this->setVersion($aversion[1]); + $this->setBrowser(self::BROWSER_GALEON); + return true; + } + return false; + } + + /** + * Determine if the browser is Konqueror or not (last updated 1.7) + * @return boolean True if the browser is Konqueror otherwise false + */ + protected function checkBrowserKonqueror() + { + if (stripos($this->getUserAgent(), 'Konqueror') !== false) + { + $aresult = explode(' ', stristr($this->getUserAgent(), 'Konqueror')); + $aversion = explode('/', $aresult[0]); + $this->setVersion($aversion[1]); + $this->setBrowser(self::BROWSER_KONQUEROR); + return true; + } + return false; + } + + /** + * Determine if the browser is iCab or not (last updated 1.7) + * @return boolean True if the browser is iCab otherwise false + */ + protected function checkBrowserIcab() + { + if (stripos($this->getUserAgent(), 'icab') !== false) + { + $aversion = explode(' ', stristr(str_replace('/', ' ', $this->getUserAgent()), 'icab')); + $this->setVersion($aversion[1]); + $this->setBrowser(self::BROWSER_ICAB); + return true; + } + return false; + } + + /** + * Determine if the browser is OmniWeb or not (last updated 1.7) + * @return boolean True if the browser is OmniWeb otherwise false + */ + protected function checkBrowserOmniWeb() + { + if (stripos($this->getUserAgent(), 'omniweb') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'omniweb')); + $aversion = explode(' ', isset($aresult[1]) ? $aresult[1] : ""); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_OMNIWEB); + return true; + } + return false; + } + + /** + * Determine if the browser is Phoenix or not (last updated 1.7) + * @return boolean True if the browser is Phoenix otherwise false + */ + protected function checkBrowserPhoenix() + { + if (stripos($this->getUserAgent(), 'Phoenix') !== false) + { + $aversion = explode('/', stristr($this->getUserAgent(), 'Phoenix')); + $this->setVersion($aversion[1]); + $this->setBrowser(self::BROWSER_PHOENIX); + return true; + } + return false; + } + + /** + * Determine if the browser is Firebird or not (last updated 1.7) + * @return boolean True if the browser is Firebird otherwise false + */ + protected function checkBrowserFirebird() + { + if (stripos($this->getUserAgent(), 'Firebird') !== false) + { + $aversion = explode('/', stristr($this->getUserAgent(), 'Firebird')); + $this->setVersion($aversion[1]); + $this->setBrowser(self::BROWSER_FIREBIRD); + return true; + } + return false; + } + + /** + * Determine if the browser is Netscape Navigator 9+ or not (last updated 1.7) + * NOTE: (http://browser.netscape.com/ - Official support ended on March 1st, 2008) + * @return boolean True if the browser is Netscape Navigator 9+ otherwise false + */ + protected function checkBrowserNetscapeNavigator9Plus() + { + if (stripos($this->getUserAgent(), 'Firefox') !== false && preg_match('/Navigator\/([^ ]*)/i', $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_NETSCAPE_NAVIGATOR); + return true; + } else if (stripos($this->getUserAgent(), 'Firefox') === false && preg_match('/Netscape6?\/([^ ]*)/i', $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_NETSCAPE_NAVIGATOR); + return true; + } + return false; + } + + /** + * Determine if the browser is Shiretoko or not (https://wiki.mozilla.org/Projects/shiretoko) (last updated 1.7) + * @return boolean True if the browser is Shiretoko otherwise false + */ + protected function checkBrowserShiretoko() + { + if (stripos($this->getUserAgent(), 'Mozilla') !== false && preg_match('/Shiretoko\/([^ ]*)/i', $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_SHIRETOKO); + return true; + } + return false; + } + + /** + * Determine if the browser is Ice Cat or not (http://en.wikipedia.org/wiki/GNU_IceCat) (last updated 1.7) + * @return boolean True if the browser is Ice Cat otherwise false + */ + protected function checkBrowserIceCat() + { + if (stripos($this->getUserAgent(), 'Mozilla') !== false && preg_match('/IceCat\/([^ ]*)/i', $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_ICECAT); + return true; + } + return false; + } + + /** + * Determine if the browser is Nokia or not (last updated 1.7) + * @return boolean True if the browser is Nokia otherwise false + */ + protected function checkBrowserNokia() + { + if (preg_match("/Nokia([^\/]+)\/([^ SP]+)/i", $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[2]); + if (stripos($this->getUserAgent(), 'Series60') !== false || strpos($this->getUserAgent(), 'S60') !== false) + { + $this->setBrowser(self::BROWSER_NOKIA_S60); + } else + { + $this->setBrowser(self::BROWSER_NOKIA); + } + $this->setMobile(true); + return true; + } + return false; + } + + /** + * Determine if the browser is Firefox or not (last updated 1.7) + * @return boolean True if the browser is Firefox otherwise false + */ + protected function checkBrowserFirefox() + { + if (stripos($this->getUserAgent(), 'safari') === false) + { + if (preg_match("/Firefox[\/ \(]([^ ;\)]+)/i", $this->getUserAgent(), $matches)) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_FIREFOX); + return true; + } else if (preg_match("/Firefox$/i", $this->getUserAgent(), $matches)) + { + $this->setVersion(""); + $this->setBrowser(self::BROWSER_FIREFOX); + return true; + } + } + return false; + } + + /** + * Determine if the browser is Firefox or not (last updated 1.7) + * @return boolean True if the browser is Firefox otherwise false + */ + protected function checkBrowserIceweasel() + { + if (stripos($this->getUserAgent(), 'Iceweasel') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Iceweasel')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_ICEWEASEL); + return true; + } + return false; + } + + /** + * Determine if the browser is Mozilla or not (last updated 1.7) + * @return boolean True if the browser is Mozilla otherwise false + */ + protected function checkBrowserMozilla() + { + $agent = $this->getUserAgent(); + if (stripos($agent, 'mozilla') !== false && preg_match('/rv:[0-9].[0-9][a-b]?/i', $agent) && stripos($agent, 'netscape') === false) + { + $aversion = explode(' ', stristr($agent, 'rv:')); + preg_match('/rv:[0-9].[0-9][a-b]?/i', $agent, $aversion); + $this->setVersion(str_replace('rv:', '', $aversion[0])); + $this->setBrowser(self::BROWSER_MOZILLA); + return true; + } else if (stripos($agent, 'mozilla') !== false && preg_match('/rv:[0-9]\.[0-9]/i', $agent) && stripos($agent, 'netscape') === false) + { + $aversion = explode('', stristr($agent, 'rv:')); + $this->setVersion(str_replace('rv:', '', $aversion[0])); + $this->setBrowser(self::BROWSER_MOZILLA); + return true; + } else if (stripos($agent, 'mozilla') !== false && preg_match('/mozilla\/([^ ]*)/i', $agent, $matches) && stripos($agent, 'netscape') === false) + { + $this->setVersion($matches[1]); + $this->setBrowser(self::BROWSER_MOZILLA); + return true; + } + return false; + } + + /** + * Determine if the browser is Lynx or not (last updated 1.7) + * @return boolean True if the browser is Lynx otherwise false + */ + protected function checkBrowserLynx() + { + if (stripos($this->getUserAgent(), 'lynx') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Lynx')); + $aversion = explode(' ', (isset($aresult[1]) ? $aresult[1] : "")); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_LYNX); + return true; + } + return false; + } + + /** + * Determine if the browser is Amaya or not (last updated 1.7) + * @return boolean True if the browser is Amaya otherwise false + */ + protected function checkBrowserAmaya() + { + if (stripos($this->getUserAgent(), 'amaya') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Amaya')); + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + $this->setBrowser(self::BROWSER_AMAYA); + return true; + } + return false; + } + + /** + * Determine if the browser is Safari or not (last updated 1.7) + * @return boolean True if the browser is Safari otherwise false + */ + protected function checkBrowserSafari() + { + if (stripos($this->getUserAgent(), 'Safari') !== false && stripos($this->getUserAgent(), 'iPhone') === false && stripos($this->getUserAgent(), 'iPod') === false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Version')); + if (isset($aresult[1])) + { + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $this->setVersion(self::VERSION_UNKNOWN); + } + $this->setBrowser(self::BROWSER_SAFARI); + return true; + } + return false; + } + + /** + * Determine if the browser is iPhone or not (last updated 1.7) + * @return boolean True if the browser is iPhone otherwise false + */ + protected function checkBrowseriPhone() + { + if (stripos($this->getUserAgent(), 'iPhone') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Version')); + if (isset($aresult[1])) + { + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $this->setVersion(self::VERSION_UNKNOWN); + } + $this->setMobile(true); + $this->setBrowser(self::BROWSER_IPHONE); + return true; + } + return false; + } + + /** + * Determine if the browser is iPod or not (last updated 1.7) + * @return boolean True if the browser is iPod otherwise false + */ + protected function checkBrowseriPad() + { + if (stripos($this->getUserAgent(), 'iPad') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Version')); + if (isset($aresult[1])) + { + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $this->setVersion(self::VERSION_UNKNOWN); + } + $this->setMobile(true); + $this->setBrowser(self::BROWSER_IPAD); + return true; + } + return false; + } + + /** + * Determine if the browser is iPod or not (last updated 1.7) + * @return boolean True if the browser is iPod otherwise false + */ + protected function checkBrowseriPod() + { + if (stripos($this->getUserAgent(), 'iPod') !== false) + { + $aresult = explode('/', stristr($this->getUserAgent(), 'Version')); + if (isset($aresult[1])) + { + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $this->setVersion(self::VERSION_UNKNOWN); + } + $this->setMobile(true); + $this->setBrowser(self::BROWSER_IPOD); + return true; + } + return false; + } + + /** + * Determine if the browser is Android or not (last updated 1.7) + * @return boolean True if the browser is Android otherwise false + */ + protected function checkBrowserAndroid() + { + if (stripos($this->getUserAgent(), 'Android') !== false) + { + $aresult = explode(' ', stristr($this->getUserAgent(), 'Android')); + if (isset($aresult[1])) + { + $aversion = explode(' ', $aresult[1]); + $this->setVersion($aversion[0]); + } else + { + $this->setVersion(self::VERSION_UNKNOWN); + } + $this->setMobile(true); + $this->setBrowser(self::BROWSER_ANDROID); + return true; + } + return false; + } + + /** + * Determine the user's platform (last updated 1.7) + */ + protected function checkPlatform() + { + $agent = $this->getUserAgent(); + + if (stripos($agent, 'windows') !== false) + $this->attributes->platform = self::PLATFORM_WINDOWS; + else if (stripos($agent, 'iPad') !== false) + $this->attributes->platform = self::PLATFORM_IPAD; + else if (stripos($agent, 'iPod') !== false) + $this->attributes->platform = self::PLATFORM_IPOD; + else if (stripos($agent, 'iPhone') !== false) + $this->attributes->platform = self::PLATFORM_IPHONE; + elseif (stripos($agent, 'mac') !== false) + $this->attributes->platform = self::PLATFORM_APPLE; + elseif (stripos($agent, 'android') !== false) + $this->attributes->platform = self::PLATFORM_ANDROID; + elseif (stripos($agent, 'linux') !== false) + $this->attributes->platform = self::PLATFORM_LINUX; + else if (stripos($agent, 'Nokia') !== false) + $this->attributes->platform = self::PLATFORM_NOKIA; + else if (stripos($agent, 'BlackBerry') !== false) + $this->attributes->platform = self::PLATFORM_BLACKBERRY; + elseif (stripos($agent, 'FreeBSD') !== false) + $this->attributes->platform = self::PLATFORM_FREEBSD; + elseif (stripos($agent, 'OpenBSD') !== false) + $this->attributes->platform = self::PLATFORM_OPENBSD; + elseif (stripos($agent, 'NetBSD') !== false) + $this->attributes->platform = self::PLATFORM_NETBSD; + elseif (stripos($agent, 'OpenSolaris') !== false) + $this->attributes->platform = self::PLATFORM_OPENSOLARIS; + elseif (stripos($agent, 'SunOS') !== false) + $this->attributes->platform = self::PLATFORM_SUNOS; + elseif (stripos($agent, 'OS\/2') !== false) + $this->attributes->platform = self::PLATFORM_OS2; + elseif (stripos($agent, 'BeOS') !== false) + $this->attributes->platform = self::PLATFORM_BEOS; + elseif (stripos($agent, 'win') !== false) + $this->attributes->platform = self::PLATFORM_WINDOWS; + } + +} +/** + * Class event to report on detection event + */ +class EWebBrowserEvent extends CEvent{ + public $agent; + public $browser_name; + public $version; + public $platform; + public $os; + public $is_aol; + public $is_mobile; + public $is_robot; + public $aol_version; +} +?> diff --git a/helpers/ECurrencyHelper/ECurrencyHelper.php b/helpers/ECurrencyHelper/ECurrencyHelper.php new file mode 100644 index 0000000..5a6dd57 --- /dev/null +++ b/helpers/ECurrencyHelper/ECurrencyHelper.php @@ -0,0 +1,262 @@ +load(); + } + /** + * Returns a specific rate value + * IMPORTANT: Rates are those from Central European Bank Only + * @param string $currencyCode the three letter currency code + * @return float the currency rate, boolean false otherwise + */ + public function getRate($currencyCode) + { + if(array_key_exists($currencyCode, $this->currencies)) + return $this->currencies[$currencyCode]; + return false; + } + /** + * Returns loaded currency rates + * IMPORTANT: Rates are those from Central European Bank Only + * @return array the currencies rates loaded + */ + public function getRates() + { + return $this->currencies; + } + /** + * Loads daily currency conversion rates from the European Central Bank + * @return boolean true if successful, false otherwise + */ + public function load() + { + $xml = $this->_request($this->uri); + + $dom = new DOMDocument(); + $dom->preserveWhiteSpace = false; + $dom->validateOnParse = false; + $dom->loadXML($xml); + + $currencies = $dom->getElementsByTagName('Cube'); + + if($currencies) + { + foreach($currencies as $c) + { + if($c->hasAttribute('currency') && $c->hasAttribute('rate')) + $this->currencies[$c->getAttribute("currency")] = $c->getAttribute("rate"); + } + $this->currencies['EUR'] = 1; + return true; + } + return false; + } + /** + * Converts one currency to another based on the EURO conversion rate + * @param string $from the currency code to convert from + * @param string $to the currency code to convert to + * @param float $amount the amount to convert + * @return float the converted amount + */ + public function convert($from, $to, $amount, $engine=self::USE_CENTRAL_EUROPEAN_BANK ) + { + if( $engine !== self::USE_CENTRAL_EUROPEAN_BANK && + $engine !== self::USE_GOOGLE && + $engine !== self::USE_YAHOO ) + throw new CException ('ECurrencyHelper', 'Unsupported conversion engine'); + + $result; + if($engine===self::USE_CENTRAL_EUROPEAN_BANK) + $result = $this->euroConvert ($from, $to, $amount); + elseif($engine===self::USE_GOOGLE) + $result = $this->googleConvert ($from, $to, $amount); + else + $result = $this->yahooConvert ($from, $to, $amount); + + return $result; + } + /** + * Converts one currency to another based on the EURO conversion rate + * @param string $from the currency code to convert from + * @param string $to the currency code to convert to + * @param float $amount the amount to convert + * @return float the converted amount + */ + protected function euroConvert($from, $to, $amount) + { + if(!array_key_exists($from, $this->currencies) || !array_key_exists($to, $this->currencies)) + throw new CException('ECurrencyHelper','Unsupported currency type'); + + if($to === 'EUR') + return (float) $amount / $this->currencies[$from]; + if($from === 'EUR') + return (float) $amount * $this->currencies[$to]; + + return (float) ($amount / $this->currencies[$from]) * $this->currencies[$to]; + } + /** + * + * Converts one currency to another based on Yahoo finance conversion rate + * Special thanks to *Aphraoh* for its contribution + * -http://www.yiiframework.com/forum/index.php?/user/6213-aphraoh/ + * @param string $from the currency code to convert from + * @param string $to the currency code to convert to + * @param float $amount the amount to convert + * @return float the converted amount false if not successful + */ + protected function yahooConvert($from, $to, $amount) + { + $uri = str_replace( + array('{FROM}', '{TO}'), + array($from, $to), $this->yahoo); + //sleep(1); //Be nice to Yahoo, they don't have a lot of hi-spec servers + $rate = $this->_request($uri); + return $rate ? (float) $rate * $amount : false; + } + /** + * Converts one currency to another based on the Google conversion calculator + * @param string $from the currency code to convert from + * @param string $to the currency code to convert to + * @param float $amount the amount to convert + * @return float the converted amount + */ + protected function googleConvert($from, $to, $amount) + { + $uri = str_replace( + array('{FROM}', '{TO}', '{AMOUNT}'), + array($from, $to, $amount), $this->google); + $raw = $this->_request($uri); + $data = explode('"', $raw); + $data = explode(' ', $data['3']); + + return trim($data[0]) !== ""? (float) $data[0] : false; + } + /** + * Requests a specific url and returns its contents + * @param string $url the uri to call + * @return string raw contents + */ + private function _request($url) + { + if (function_exists('curl_version')) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER["HTTP_USER_AGENT"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $raw_data = curl_exec($ch); + curl_close($ch); + } else // no CUrl, try differently + $raw_data = file_get_contents($url); + return $raw_data; + } + +} diff --git a/helpers/EDownloadHelper/EDownloadHelper.php b/helpers/EDownloadHelper/EDownloadHelper.php new file mode 100644 index 0000000..8795aa7 --- /dev/null +++ b/helpers/EDownloadHelper/EDownloadHelper.php @@ -0,0 +1,143 @@ + 0) + $seek_start = intval($range[0]); + if($range[1] > 0) + $seek_end = intval($range[1]); + + $data_section = true; + } + // do some cleaning before we start + ob_end_clean(); + $old_status = ignore_user_abort(true); + set_time_limit(0); + + $size = filesize( $filepath ); + + if($seek_start > ($size -1)) $seek_start = 0; + + // open the file and move pointer + // to started chunk + $res = fopen( $filepath , 'rb'); + if($seek_start) fseek($res, $seek_start); + if($seek_end < $seek_start) $seek_end = $size -1; + + + header('Content-Type: '.$mimeType); + + $contentDisposition = 'attachment'; + if($doStream == true){ + if(in_array( $extension,self::$stream_types )){ + $contentDisposition = 'inline'; + } + } + if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) { + $fileName= preg_replace('/\./', '%2e', $filename, substr_count($filename, '.') - 1); + } + header('Content-Disposition: '.$contentDisposition.'; filename="'.$filename.'"'); + header('Last-Modified: ' . date('D, d M Y H:i:s \G\M\T', filemtime( $filepath ))); + + // flushing a data section? + if( $data_section ) + { + header("HTTP/1.0 206 Partial Content"); + header("Status: 206 Partial Content"); + header('Accept-Ranges: bytes'); + header("Content-Range: bytes $seek_start-$seek_end/$size"); + header("Content-Length: " . ($seek_end - $seek_start + 1)); + + }else // nope, just + header('Content-Length: '.$size); + + $size = $seek_end - $seek_start + 1; + + while(!( connection_aborted() || connection_status() == 1) && !feof($res)) + { + print(fread($res, $buffsize*$maxSpeed)); + + flush(); + @ob_flush(); + sleep(1); + } + // close file + fclose($res); + // restore defaults + ignore_user_abort($old_status); + set_time_limit(ini_get('max_execution_time')); + + } +} \ No newline at end of file diff --git a/helpers/EIniHelper/EIniHelper.php b/helpers/EIniHelper/EIniHelper.php new file mode 100644 index 0000000..724e054 --- /dev/null +++ b/helpers/EIniHelper/EIniHelper.php @@ -0,0 +1,108 @@ +Get('Database'); + * $username = $helper->Get('Database','username'); + * + * $dataConf = EIniHelper::Load('pathtoInifile')->Get("Database"); + * + * $language = EIniHelper::Load('pathtoInifile')->Get("Parameters","language"); + * + * @copyright + * + * Copyright (c) 2011 Antonio Ramirez Cobos + * 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. + * + */ +class EIniHelper { + + private $settingsfile, $settings; + + /** + * Constructor. + * Loads the settings by parsing the ini file passed + * in the constructor parameter. + * @param string $settingsfile + */ + function __construct($settingsfile) + { + if(!file_exists($settingsfile)) + throw new CException(Yii::t('EIniHelper','INI file not found')); + + $this->settingsfile = $settingsfile; + $this->settings = parse_ini_file($this->settingsfile, true); + + } + + /** + * Settings::Load + * + * Singleton functionality that creates one instance per loaded settings + * file so that ini parsing needs to happen only once. + * @param string $settingsfile file to read for settings + */ + public static function Load($settingsfile= 'settings.ini') + { + static $instances = array(); + if(!array_key_exists($settingsfile, $instances)) { + $instances[$settingsfile] = new EIniHelper($settingsfile); + } + return($instances[$settingsfile]); + } + + /** + * Settings::Get + * + * Gets an array of parameters for a key of settings file or + * gets one specific setting under a key. + * @param string $param + * @return string subsection | array ini section + */ + function Get($section, $subsection = false) + { + if($this->settings === false) throw new CException(Yii::t('EIniHelper','error reading INI file')); + return ($subsection) ? $this->settings[$section][$subsection] : $this->settings[$section]; + } + +} + +?> \ No newline at end of file diff --git a/helpers/EIniHelper/settings.ini b/helpers/EIniHelper/settings.ini new file mode 100755 index 0000000..8873402 --- /dev/null +++ b/helpers/EIniHelper/settings.ini @@ -0,0 +1,11 @@ +; This is a test ini file. You can create as many sections as you wish + +[Database] +dbtype = mysql +host = localhost +username = root +password = testpassword +database = testdatabase + +[Parameters] +webmaster = test@test.com diff --git a/validators /EABARoutingNumberValidator/EABARoutingNumberValidator.php b/validators /EABARoutingNumberValidator/EABARoutingNumberValidator.php new file mode 100644 index 0000000..61aed12 --- /dev/null +++ b/validators /EABARoutingNumberValidator/EABARoutingNumberValidator.php @@ -0,0 +1,84 @@ +$attribute; + if($this->allowEmpty && $this->isEmpty($value)) + return; + + $return = $this->validateABARoutingNumber($value); + + if( true !== $return ) + { + $message=$this->message!==null? $this->message:Yii::t('EABAValidator',"'{value}' has failed the ABA Routine Number check", array('{value}'=>$value)); + $this->addError($object,$attribute,$message); + } + } + /** + * + * The check-digit Routing Number function + * @param string $routingNumber + * @see http://www.brainjar.com/js/validation/ + */ + function validateABARoutingNumber( $routingNumber ) { + $routingNumber = preg_replace('[\D]', '', $routingNumber); + + $len = strlen($routingNumber); + + if( $len !== 9 ) return false; + + $checkSum = 0; + + for ($i = 0; $i < $len; $i+= 3 ) { + $checkSum += ($routingNumber[$i] * 3) + + ($routingNumber[$i+1] * 7) + + ($routingNumber[$i+2]); + } + + return ($checkSum !== 0 && ($checkSum % 10) === 0); + } +} \ No newline at end of file diff --git a/validators /ECCValidator/ECCValidator.php b/validators /ECCValidator/ECCValidator.php new file mode 100644 index 0000000..8741251 --- /dev/null +++ b/validators /ECCValidator/ECCValidator.php @@ -0,0 +1,209 @@ +'/^5[1-5][0-9]{14}$/', + self::VISA=>'/^4[0-9]{12}([0-9]{3})?$/', + self::AMERICAN_EXPRESS=>'/^3[47][0-9]{13}$/', + self::DINERS_CLUB=>'/^3(0[0-5]|[68][0-9])[0-9]{11}$/', + self::DISCOVER=>'/^(6011\d{12}|65\d{14})$/', + self::JCB=>'/^(3[0-9]{4}|2131|1800)[0-9]{11}$/', + self::VOYAGER=>'/^8699[0-9]{11}$/', + self::SOLO=>'/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/', + self::MAESTRO=>'/^(?:5020|6\\d{3})\\d{12}$/', + self::SWITCH_CARD=>'/^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$/', + self::ELECTRON=>'/^(?:417500|4026\\d{2}|4917\\d{2}|4913\\d{2}|4508\\d{2}|4844\\d{2})\\d{10}$/', + self::LASER=>'/^(?:6304|6706|6771|6709)\\d{12}(\\d{2,3})?$/', + self::ALL=>'/^(5[1-5][0-9]{14}|4[0-9]{12}([0-9]{3})?|3[47][0-9]{13}|3(0[0-5]|[68][0-9])[0-9]{11}|(6011\d{12}|65\d{14})|(3[0-9]{4}|2131|1800)[0-9]{11}|2(?:014|149)\\d{11}|8699[0-9]{11}|(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?|(?:5020|6\\d{3})\\d{12}|56(10\\d\\d|022[1-5])\\d{10}|(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)|(?:417500|4026\\d{2}|4917\\d{2}|4913\\d{2}|4508\\d{2}|4844\\d{2})\\d{10}|(?:417500|4026\\d{2}|4917\\d{2}|4913\\d{2}|4508\\d{2}|4844\\d{2})\\d{10})$/' + ); + /** + * + * @var string set with selected Credit Card type to check -ie ECCValidator::MAESTRO + */ + public $format = self::ALL; + /** + * @var boolean whether the attribute value can be null or empty. Defaults to true, + * meaning that if the attribute is empty, it is considered valid. + */ + public $allowEmpty=true; + /** + * (non-PHPdoc) + * @see CValidator::validateAttribute() + */ + protected function validateAttribute($object,$attribute){ + + $value=$object->$attribute; + if($this->allowEmpty && $this->isEmpty($value)) + return; + + + if(!$this->validateNumber($value)) + { + $message=$this->message!==null?$this->message:Yii::t('ECCValidator','{attribute} is not a valid Credit Card number.'); + $this->addError($object,$attribute,$message); + } + } + /** + * + * Validates a Credit Card number + * @param string $creditCardNumber + */ + public function validateNumber($creditCardNumber){ + + if(!$this->checkType()) + throw new CException(Yii::t('ECCValidator','The "format" property must be specified with a supported Credit Card format.')); + + $creditCardNumber = preg_replace('/[ -]+/', '', $creditCardNumber); + + return $this->checkFormat($creditCardNumber) && $this->mod10($creditCardNumber); + } + /** + * + * Validates a Credit Card date + * @param integer $creditCardExpiredMonth + * @param integer $creditCardExpiredYear + */ + public function validateDate($creditCardExpiredMonth, $creditCardExpiredYear){ + + $currentYear = intval(date('Y')); + + if(is_scalar($creditCardExpiredMonth)) $creditCardExpiredMonth = intval($creditCardExpiredMonth); + if(is_scalar($creditCardExpiredYear)) $creditCardExpiredYear = intval($creditCardExpiredYear); + + return is_integer($creditCardExpiredMonth) && $creditCardExpiredMonth >= 1 && $creditCardExpiredMonth <= 12 && + is_integer( $creditCardExpiredYear ) && $creditCardExpiredYear > $currentYear && $creditCardExpiredYear < $currentYear+10; + } + /** + * + * Validates Credit Card holder + * @param string $creditCardHolder + */ + public function validateName($creditCardHolder){ + + return !empty( $creditCardHolder ) && eregi('^[A-Z ]+$', $creditCardHolder); + } + /** + * + * Validates holder, number, and dates of Credit Card numbers + * + * @param string $creditCardHolder + * @param string $creditCardNumber + * @param integer $creditCardExpiredMonth + * @param integer $creditCardExpiredYear + */ + public function validateAll($creditCardHolder, $creditCardNumber, $creditCardExpiredMonth, $creditCardExpiredYear){ + + return $this->validateName($creditCardHolder) && $this->validateNumber($creditCardNumber) && $this->validateDate($creditCardExpiredMonth, $creditCardExpiredYear); + + } + /** + * + * Checks Credit Card Prefixes + * + * @access private + * @param string cardNumber + * @return boolean true|false + */ + protected function checkFormat($cardNumber) + { + return preg_match('/^[0-9]+$/',$cardNumber) && preg_match( $this->patterns[$this->format], $cardNumber ); + } + /** + * + * Check credit card number by Mod 10 algorithm + * + * @access private + * @param string carNumber + * @return boolean + * @see http://en.wikipedia.org/wiki/Luhn_algorithm#Mod_10.2B5_Variant + */ + protected function mod10($cardNumber) + { + $cardNumber = strrev($cardNumber); + $numSum = 0; + for($i = 0; $i < strlen($cardNumber); $i++) { + $currentNum = substr($cardNumber, $i, 1); + if ($i % 2 == 1) { + $currentNum *= 2; + } + if ($currentNum > 9) { + $firstNum = $currentNum % 10; + $secondNum = ($currentNum - $firstNum) / 10; + $currentNum = $firstNum + $secondNum; + } + $numSum += $currentNum; + } + return ($numSum % 10 == 0); + } + /** + * + * Checks if Credit Card Format is a supported one + * and builds new pattern format in case user has + * a mixed match search (mastercard|visa) + * + * @access private + * @return boolean + */ + protected function checkType(){ + + if(is_scalar($this->format)){ + return array_key_exists($this->format, $this->patterns); + } + else if (is_array($this->format)){ + $pattern = array(); + foreach($this->format as $f){ + if(!array_key_exists($f, $this->patterns)) return false; + $pattern[] = substr($this->patterns[$f], 2,strlen($this->patterns[$f])-4); + } + $this->format = 'custom'; + $this->patterns[$this->format] = '/^('.join('|',$pattern).')$/'; + return true; + } + return false; + + } +} \ No newline at end of file diff --git a/validators /EConditionalValidator/EConditionalValidator.php b/validators /EConditionalValidator/EConditionalValidator.php new file mode 100644 index 0000000..82f5d95 --- /dev/null +++ b/validators /EConditionalValidator/EConditionalValidator.php @@ -0,0 +1,144 @@ + + * array('attribute','required', 'on'=>'create') + * + * or + * array( + * 'group'=>array( + * array('attribute','required'), + * array('attribute','email') + * ) + * ) + * + * @var array $conditionalRules + * own rule + */ + public $conditionalRules = array(); + /** + * @var array $rule the rule + */ + public $rule = array(); + /** + * @var boolean $skipConditional whether to skip conditional validations + */ + public $skipConditional = false; + /** + * + * Allows the insertion of the JS code that will be executed for + * client validation + * @var string $clientValidationJS + */ + public $clientValidationJS; + + /** + * Validates the attribute of the object. + * If there is any error, the error message is added to the object. + * @param CModel the object being validated + * @param string the attribute being validated + */ + protected function validateAttribute($object, $attribute) + { + $obj = get_class($object); + $obj = new $obj(); + $obj->setAttributes($object->getAttributes()); + + if (!$this->skipConditional && !$this->validateConditional($obj, $this->conditionalRules)) + return false; + + $validator = CValidator::createValidator($this->rule[0], $object, $attribute, array_splice($this->rule, 1)); + $validator->validate($object); + $obj = null; + } + /** + * + * @param CModel $object the object to be validated + * @param mixed $rule the rules to validate the object against + * @return boolean false if it has errors, true otherwise + */ + protected function validateConditional(&$object, $rule) + { + if (isset($rule['group'])) + { + if(is_array($rule['group'])) + { + foreach ($rule['group'] as $r) + { + if (is_array($r)) + { + $val = $this->validateConditional($object, $r); + if (!$val) + return false; + } + else continue; + } + }else + throw new CException (Yii::t('EConditionalValidator','Group must be an array of rules')); + } + else + { + list($attributes, $conditionalValidator) = $rule; + + $parameters = array_splice($rule, 2); + + $validator = CValidator::createValidator($conditionalValidator, $object, $attributes, $parameters); + + $validator->validate($object); + if ($object->hasErrors()) + { + $object->clearErrors(); + return false; + } + } + return true; + } + /** + * Returns the JavaScript needed for performing client-side validation. + * Do not override this method if the validator does not support client-side validation. + * Two predefined JavaScript variables can be used: + *
      + *
    • value: the value to be validated
    • + *
    • messages: an array used to hold the validation error messages for the value
    • + *
    + * @param CModel $object the data object being validated + * @param string $attribute the name of the attribute to be validated. + * @return string the client-side validation script. Null if the validator does not support client-side validation. + * @see CActiveForm::enableClientValidation + */ + public function clientValidateAttribute($object, $attribute) + { + return $this->clientValidationJS ? $this->clientValidationJS : null; + } + +} \ No newline at end of file diff --git a/validators /EIBANValidator/EIBANValidator.php b/validators /EIBANValidator/EIBANValidator.php new file mode 100644 index 0000000..8f78041 --- /dev/null +++ b/validators /EIBANValidator/EIBANValidator.php @@ -0,0 +1,204 @@ + "Unknown country within the IBAN '{value}'", + self::WRONGFORMAT => "'{value}' has a false IBAN format", + self::WRONGLENGTH => "'{value}' has wrong IBAN length for specified country", + self::CHECKFAILED => "'{value}' has failed the IBAN check", + ); + + protected $_lengths = array( + 'AD'=>24,'AT'=>20,'BA'=>20,'BE'=>16,'BG'=>22, + 'CH'=>21,'CS'=>22,'CY'=>28,'CZ'=>24,'DE'=>22, + 'DK'=>18,'EE'=>20,'ES'=>24,'FR'=>27,'FI'=>18, + 'GB'=>22,'GI'=>23,'GR'=>27,'HR'=>21,'HU'=>28, + 'IE'=>22,'IS'=>26,'IT'=>27,'LI'=>21,'LU'=>20, + 'LT'=>20,'LV'=>21,'MC'=>27,'ME'=>22,'MU'=>30, + 'MK'=>19,'MT'=>31,'NC'=>27,'NL'=>18,'NO'=>15, + 'PF'=>27,'PL'=>28,'PT'=>25,'PM'=>27,'RO'=>24, + 'RS'=>22,'SA'=>24,'SE'=>24,'SI'=>19,'SK'=>24, + 'SM'=>27,'TF'=>27,'TN'=>24,'TR'=>26,'YT'=>27, + 'WF'=>27 + ); + protected $_patterns = array( + 'AD' => '/^AD[0-9]{2}[0-9]{8}[A-Z0-9]{12}$/', // Andorra + 'AT' => '/^AT[0-9]{2}[0-9]{5}[0-9]{11}$/', // Austria + 'BA' => '/^BA[0-9]{2}[0-9]{6}[0-9]{10}$/', // Bosnia and Herzegovina + 'BE' => '/^BE[0-9]{2}[0-9]{3}[0-9]{9}$/', // Belgium + 'BG' => '/^BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}$/', // Bulgaria + 'CH' => '/^CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/', // Switzerland + // CS to Serbia and Montenegro until the split into rs (Serbia) and me (Montenegro) + 'CS' => '/^CS[0-9]{2}[0-9]{3}[0-9]{15}$/', // Serbia and Montenegro + 'CY' => '/^CY[0-9]{2}[0-9]{8}[A-Z0-9]{16}$/', // Cyrus + 'CZ' => '/^CZ[0-9]{2}[0-9]{4}[0-9]{16}$/', // Czech Republic + 'DE' => '/^DE[0-9]{2}[0-9]{8}[0-9]{10}$/', // Germany + 'DK' => '/^DK[0-9]{2}[0-9]{4}[0-9]{10}$/', // Denmark + 'EE' => '/^EE[0-9]{2}[0-9]{4}[0-9]{12}$/', // Estonia + 'ES' => '/^ES[0-9]{2}[0-9]{8}[0-9]{12}$/', // Spain + 'FR' => '/^FR[0-9]{2}[0-9]{10}[A-Z0-9]{13}$/', // France + 'FI' => '/^FI[0-9]{2}[0-9]{6}[0-9]{8}$/', // Finland + 'GB' => '/^GB[0-9]{2}[A-Z]{4}[0-9]{14}$/', // United Kingdom + 'GI' => '/^GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}$/', // Gibraltar + 'GR' => '/^GR[0-9]{2}[0-9]{7}[A-Z0-9]{16}$/', // Greece + 'HR' => '/^HR[0-9]{2}[0-9]{7}[0-9]{10}$/', // Croatia + 'HU' => '/^HU[0-9]{2}[0-9]{7}[0-9]{1}[0-9]{15}[0-9]{1}$/', // Hungary + 'IE' => '/^IE[0-9]{2}[A-Z0-9]{4}[0-9]{6}[0-9]{8}$/', // Ireland + 'IS' => '/^IS[0-9]{2}[0-9]{4}[0-9]{18}$/', // Iceland + 'IT' => '/^IT[0-9]{2}[A-Z]{1}[0-9]{10}[A-Z0-9]{12}$/', // Italy + 'LI' => '/^LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}$/', // Liechtenstein + 'LU' => '/^LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}$/', // Luxembourg + 'LT' => '/^LT[0-9]{2}[0-9]{5}[0-9]{11}$/', // Lithuania + 'LV' => '/^LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}$/', // Latvia + 'MC' => '/^MC(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // Monaco + 'ME' => '/^ME(\d{2})(\d{3})(\d{13})(\d{2})$/', // Montenegro + 'MU' => '/^MU(\d{2})([A-Z]{4})(\d{2})(\d{2})(\d{12})(\d{3})([A-Z]{3})$/',// Mauritius + 'MK' => '/^MK(\d{2})(\d{3})([A-Za-z0-9]{10})(\d{2})$/', // Macedonia MK07 250 1200000589 84 3n,10c,2n + 'MT' => '/^MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}$/', // Malta + 'NC' => '/^NC(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // New Caledonia + 'NL' => '/^NL[0-9]{2}[A-Z]{4}[0-9]{10}$/', // The Netherlands + 'NO' => '/^NO[0-9]{2}[0-9]{4}[0-9]{7}$/', // Norway + 'PF' => '/^PF(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // French Polynesia + 'PL' => '/^PL[0-9]{2}[0-9]{8}[0-9]{16}$/', // Poland + 'PM' => '/^PM(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // Saint Pierre et Miquelon + 'PT' => '/^PT[0-9]{2}[0-9]{8}[0-9]{13}$/', // Portugal + 'RO' => '/^RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}$/', // Romania + 'RS' => '/^RS(\d{2})(\d{3})(\d{13})(\d{2})$/', // Serbia + 'SA' => '/^SA(\d{2})(\d{2})([A-Za-z0-9]{18})$/', // Saudi Arabia + 'SE' => '/^SE[0-9]{2}[0-9]{3}[0-9]{17}$/', // Sweden + 'SI' => '/^SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}$/', // Slovenia + 'SK' => '/^SK[0-9]{2}[0-9]{4}[0-9]{16}$/', // Slovak Republic + 'SM' => '/^SM(\d{2})([A-Z]{1})(\d{5})(\d{5})([A-Za-z0-9]{12})$/', // San Marino + 'TF' => '/^TF(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // French Southern Territories + 'TN' => '/^TN[0-9]{2}[0-9]{5}[0-9]{15}$/', // Tunisia + 'TR' => '/^TR[0-9]{2}[0-9]{5}[A-Z0-9]{17}$/', // Turkey + 'YT' => '/^YT(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/', // Mayotte + 'WF' => '/^WF(\d{2})(\d{5})(\d{5})([A-Za-z0-9]{11})(\d{2})$/' // Wallis and Futuna Islands + ); + + + /** + * @var boolean whether the attribute value can be null or empty. Defaults to true, + * meaning that if the attribute is empty, it is considered valid. + */ + public $allowEmpty=true; + /** + * (non-PHPdoc) + * @see CValidator::validateAttribute() + */ + protected function validateAttribute($object,$attribute){ + + $value=$object->$attribute; + if($this->allowEmpty && $this->isEmpty($value)) + return; + + $return = $this->validateIBAN($value); + + if( true !== $return ) + { + $message=$this->message!==null?$this->message:$this->getErrorMessage($return, $value); + $this->addError($object,$attribute,$message); + } + } + /** + * + * Validates IBAN Number + * @param string $ibanNumber + */ + public function validateIBAN( $ibanNumber ){ + // remove non-basic roman letter or digit characters + $ibanNumber = preg_replace('/[^A-Z0-9]/', '', ltrim(strtoupper($ibanNumber))); + // remove IBAN if any + $ibanNumber = preg_replace('/^IBAN/','',$ibanNumber); + // get country part + $country = substr($ibanNumber, 0, 2); + echo $ibanNumber.'
    '; + if(!array_key_exists($country, $this->_patterns)) + return self::NOTSUPPORTED; + // check pattern + if(!preg_match($this->_patterns[$country], $ibanNumber)) + return self::WRONGFORMAT; + // check length + if(strlen($ibanNumber) != $this->_lengths[$country]){ + echo strlen($ibanNumber).' '.$ibanNumber.'
    '; + return self::WRONGLENGTH; + + } + + // verify checksum + // move first four chars (country code and checksum) to the end of the string + $format = substr($ibanNumber, 4) . substr($ibanNumber, 0, 4); + + $format = str_replace( + range('A','Z'), + array('10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', + '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'), + $format); + // perform MOD97-10 checksum calculation + $temp = intval(substr($format,0,1)); + $len = strlen($format); + for ($pos = 1; $pos < $len; ++$pos){ + $temp *= 10; + $temp += intval(substr($format,$pos,1)); + $temp %=97; + } + + if($temp != 1){ + return self::CHECKFAILED; + } + return true; + } + /** + * + * Returns a formatted error message + * @param string $error + * @param string $ibanNumber + */ + public function getErrorMessage( $error, $ibanNumber ){ + return Yii::t('EIBANValidator',$this->_messages[$error], array('{value}'=>$ibanNumber)); + } +} \ No newline at end of file diff --git a/widgets/EDateRangePicker/EDateRangePicker.php b/widgets/EDateRangePicker/EDateRangePicker.php new file mode 100644 index 0000000..6a5d204 --- /dev/null +++ b/widgets/EDateRangePicker/EDateRangePicker.php @@ -0,0 +1,112 @@ + + * $this->widget('EDateRangePicker', array( + * 'name'=>'publishDate', + * // additional javascript options for the date picker plugin + * 'options'=>array( + * 'arrows'=>true, + * ), + * 'htmlOptions'=>array( + * 'style'=>'height:20px;' + * ), + * )); + * + * + * By configuring the {@link options} property, you may specify the options + * that need to be passed to the daterangepicker plugin. Please refer to + * the {@link http://www.filamentgroup.com/lab/date_range_picker_using_jquery_ui_16_and_jquery_ui_css_framework/ + * Date Range Picker} documentation + * for possible options (name-value pairs). + * + * @author Antonio Ramirez + * + */ +class EDateRangePicker extends CJuiInputWidget { + + /** + * @var string the locale ID (eg 'fr', 'de') for the language to be used by the date picker. + * If this property is not set, I18N will not be involved. That is, the date picker will show in English. + * You can force English language by setting the language attribute as '' (empty string) + */ + public $language; + + /** + * @var string The i18n Jquery UI script file. It uses scriptUrl property as base url. + */ + public $i18nScriptFile = 'jquery-ui-i18n.min.js'; + + public function init() + { + parent::init(); + $this->registerScripts(); + } + + /** + * Run this widget. + * This method registers necessary javascript and renders the needed HTML code. + */ + public function run() + { + + list($name, $id) = $this->resolveNameID(); + + if (isset($this->htmlOptions['id'])) + $id = $this->htmlOptions['id']; + else + $this->htmlOptions['id'] = $id; + if (isset($this->htmlOptions['name'])) + $name = $this->htmlOptions['name']; + else + $this->htmlOptions['name'] = $name; + + if ($this->hasModel()) + echo CHtml::activeTextField($this->model, $this->attribute, $this->htmlOptions); + else + echo CHtml::textField($name, $this->value, $this->htmlOptions); + + + $options = CJavaScript::encode($this->options); + $js = "jQuery('#{$id}').daterangepicker($options);"; + + + $cs = Yii::app()->getClientScript(); + if ($this->language != '' && $this->language != 'en') + { + $this->registerScriptFile($this->i18nScriptFile); + $js .= "setTimeout(function(){jQuery('.range-start, .range-end').datepicker('option', jQuery.datepicker.regional['{$this->language}']);},500);"; + } + $cs->registerScript(__CLASS__ . '#' . $id, $js, CClientScript::POS_READY); + } + + /** + * Registers required scripts + */ + protected function registerScripts() + { + $basePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . + 'assets' . DIRECTORY_SEPARATOR . + 'daterange' . DIRECTORY_SEPARATOR; + $baseUrl = Yii::app()->getAssetManager()->publish($basePath, false, 0, YII_DEBUG); + + $scriptFile = '/jquery.daterangepicker.js'; + $cssFile = '/ui.daterangepicker.css'; + + $cs = Yii::app()->clientScript; + $cs->registerScriptFile($baseUrl . $scriptFile); + $cs->registerCssFile($baseUrl . $cssFile); + } + + +} + +?> diff --git a/widgets/EDateRangePicker/assets/daterange/jquery.daterangepicker.js b/widgets/EDateRangePicker/assets/daterange/jquery.daterangepicker.js new file mode 100755 index 0000000..b37f740 --- /dev/null +++ b/widgets/EDateRangePicker/assets/daterange/jquery.daterangepicker.js @@ -0,0 +1,746 @@ +/** + * -------------------------------------------------------------------- + * jQuery-Plugin "daterangepicker.jQuery.js" + * modified by Antonio Ramirez http://www.ramirezcobos.com + * 10.26.2011 Enhanced plugin to work with multiple instances + * 10.27.2011 Enhanced plugin to work with internationalization -with Yii extension + * 10.28.2011 Enhanced plugin for collision detection + * + * by Scott Jehl, scott@filamentgroup.com + * http://www.filamentgroup.com + * reference article: http://www.filamentgroup.com/lab/update_date_range_picker_with_jquery_ui/ + * demo page: http://www.filamentgroup.com/examples/daterangepicker/ + * + * Copyright (c) 2008 Filament Group, Inc + * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. + * + * Dependencies: jquery, jquery UI datepicker, date.js library (included at bottom), jQuery UI CSS Framework + * Changelog: + * 10.23.2008 initial Version + * 11.12.2008 changed dateFormat option to allow custom date formatting (credit: http://alexgoldstone.com/) + * 01.04.09 updated markup to new jQuery UI CSS Framework + * 01.19.2008 changed presets hash to support different text + * -------------------------------------------------------------------- + */ +jQuery.fn.daterangepicker = function(settings){ + var rangeInput = jQuery(this); + var id = rangeInput.attr('id'); + var rid = 'date-range-picker-'+id; + var sid = 'date-range-picker-start-'+id; + var eid = 'date-range-picker-end-'+id; + //defaults + var options = jQuery.extend({ + presetRanges: [ + {text: 'Today', dateStart: 'today', dateEnd: 'today' }, + {text: 'Last 7 days', dateStart: 'today-7days', dateEnd: 'today' }, + {text: 'Month to date', dateStart: function(){ return Date.parse('today').moveToFirstDayOfMonth(); }, dateEnd: 'today' }, + {text: 'Year to date', dateStart: function(){ var x= Date.parse('today'); x.setMonth(0); x.setDate(1); return x; }, dateEnd: 'today' }, + //extras: + {text: 'The previous Month', dateStart: function(){ return Date.parse('1 month ago').moveToFirstDayOfMonth(); }, dateEnd: function(){ return Date.parse('1 month ago').moveToLastDayOfMonth(); } } + //{text: 'Tomorrow', dateStart: 'Tomorrow', dateEnd: 'Tomorrow' }, + //{text: 'Ad Campaign', dateStart: '03/07/08', dateEnd: 'Today' }, + //{text: 'Last 30 Days', dateStart: 'Today-30', dateEnd: 'Today' }, + //{text: 'Next 30 Days', dateStart: 'Today', dateEnd: 'Today+30' }, + //{text: 'Our Ad Campaign', dateStart: '03/07/08', dateEnd: '07/08/08' } + ], + //presetRanges: array of objects for each menu preset. + //Each obj must have text, dateStart, dateEnd. dateStart, dateEnd accept date.js string or a function which returns a date object + presets: { + specificDate: 'Specific Date', + allDatesBefore: 'All Dates Before', + allDatesAfter: 'All Dates After', + dateRange: 'Date Range' + }, + rangeStartTitle: 'Start date', + rangeEndTitle: 'End date', + nextLinkText: 'Next', + prevLinkText: 'Prev', + doneButtonText: 'Done', + earliestDate: Date.parse('-15years'), //earliest date allowed + latestDate: Date.parse('+15years'), //latest date allowed + rangeSplitter: '-', //string to use between dates in single input + dateFormat: 'm/d/yy', // date formatting. Available formats: http://docs.jquery.com/UI/Datepicker/%24.datepicker.formatDate + closeOnSelect: false, //if a complete selection is made, close the menu + arrows: false, + posX: rangeInput.offset().left, // x position + posY: rangeInput.offset().top + rangeInput.outerHeight(), // y position + appendTo: 'body', + onClose: function(){}, + onOpen: function(){}, + onChange: function(){}, + datepickerOptions: null //object containing native UI datepicker API options + }, settings); + + + //custom datepicker options, extended by options + var datepickerOptions = { + onSelect: function(dateText, inst) { + var $rstart = $('#'+sid, rp); + var $rend = $('#'+eid, rp); + + if(!$rend.data('datepicker')) return; + var _dt_start = $rstart.length && $rstart.datepicker? $rstart.datepicker('getDate') : false; + var _dt_end = $rend.length && $rend.datepicker? $rend.datepicker('getDate') : false; + + if(rp.find('.ui-daterangepicker-specificDate').is('.ui-state-active') && inst.id !=eid){ + _dt_end = _dt_start; + $rend.datepicker('setDate',_dt_start); + } + else if(inst.id == sid) + $rend.datepicker('option','minDate',_dt_start); + else if(rp.find('.ui-daterangepicker-specificDate').is('.ui-state-active') && inst.id ==eid) + return; + + var rangeA = fDate(_dt_start); + var rangeB = fDate(_dt_end); + + //send back to input or inputs + if(rangeInput.length == 2){ + rangeInput.eq(0).val(rangeA); + rangeInput.eq(1).val(rangeB); + } + else{ + rangeInput.val((rangeA != rangeB) ? rangeA+' '+ options.rangeSplitter +' '+rangeB : rangeA); + } + //if closeOnSelect is true + if(options.closeOnSelect){ + if(!rp.find('li.ui-state-active').is('.ui-daterangepicker-dateRange') && !rp.is(':animated') ){ + hideRP(); + } + } + options.onChange(); + }, + defaultDate: +0 + }; + + //change event fires both when a calendar is updated or a change event on the input is triggered + rangeInput.change(options.onChange); + + + //datepicker options from options + options.datepickerOptions = (settings) ? jQuery.extend(datepickerOptions, settings.datepickerOptions) : datepickerOptions; + + //Capture Dates from input(s) + var inputDateA, inputDateB = Date.parse('today'); + var inputDateAtemp, inputDateBtemp; + if(rangeInput.size() == 2){ + inputDateAtemp = Date.parse( rangeInput.eq(0).val() ); + inputDateBtemp = Date.parse( rangeInput.eq(1).val() ); + if(inputDateAtemp == null){inputDateAtemp = inputDateBtemp;} + if(inputDateBtemp == null){inputDateBtemp = inputDateAtemp;} + } + else { + inputDateAtemp = Date.parse( rangeInput.val().split(options.rangeSplitter)[0] ); + inputDateBtemp = Date.parse( rangeInput.val().split(options.rangeSplitter)[1] ); + if(inputDateBtemp == null){inputDateBtemp = inputDateAtemp;} //if one date, set both + } + if(inputDateAtemp != null){inputDateA = inputDateAtemp;} + if(inputDateBtemp != null){inputDateB = inputDateBtemp;} + + + //build picker and + var rp = jQuery('
    '); + var rpPresets = (function(){ + var ul = jQuery('
      ').appendTo(rp); + jQuery.each(options.presetRanges,function(){ + jQuery('
    • '+ this.text +'
    • ') + .data('dateStart', this.dateStart) + .data('dateEnd', this.dateEnd) + .appendTo(ul); + }); + var x=0; + jQuery.each(options.presets, function(key, value) { + jQuery('
    • '+ value +'
    • ') + .appendTo(ul); + x++; + }); + + ul.find('li').hover( + function(){ + jQuery(this).addClass('ui-state-hover'); + }, + function(){ + jQuery(this).removeClass('ui-state-hover'); + }) + .click(function(){ + rp.find('.ui-state-active').removeClass('ui-state-active'); + jQuery(this).addClass('ui-state-active').clickActions(rp, rpPickers, doneBtn); + return false; + }); + return ul; + })(); + + //function to format a date string + function fDate(date){ + if(!date || !date.getDate()){return '';} + var day = date.getDate(); + var month = date.getMonth(); + var year = date.getFullYear(); + month++; // adjust javascript month + var dateFormat = options.dateFormat; + return jQuery.datepicker.formatDate( dateFormat, date ); + } + + jQuery.fn.restoreDateFromData = function(){ + if(jQuery(this).data('saveDate')){ + jQuery(this).datepicker('setDate', jQuery(this).data('saveDate')).removeData('saveDate'); + } + return this; + } + jQuery.fn.saveDateToData = function(){ + if(!jQuery(this).data('saveDate')){ + jQuery(this).data('saveDate', jQuery(this).datepicker('getDate') ); + } + return this; + } + function findPos() { + var $window = $(window), + wide = $window.width() + $window.scrollLeft(), + height = $window.height() + $window.scrollTop(), + offset = rangeInput.offset(); + + var pos = [offset.top <= $window.scrollTop(), wide <= offset.left + rp.width(), height <= offset.top + rp.height(), $window.scrollLeft() >= offset.left]; + var top = (offset.top + rangeInput[0].offsetHeight); + var left = offset.left; + + return {top:(pos[2]?top-(rangeInput.height()+rp.height()+rangeInput[0].offsetHeight):top),left:(pos[3]?$window.scrollLeft()+1:left)}; + } + //show, hide, or toggle rangepicker + function showRP(){ + var pos = findPos(); + rp.parent().css('top',pos.top); + rp.parent().css( 'left', pos.left ); + if(rp.data('state') == 'closed'){ + rp.data('state', 'open'); + rp.fadeIn(300); + options.onOpen(); + } + } + function hideRP(){ + if(rp.data('state') == 'open'){ + rp.data('state', 'closed'); + rp.fadeOut(300); + options.onClose(); + } + } + function toggleRP(){ + if( rp.data('state') == 'open' ){ hideRP(); } + else { showRP(); } + } + rp.data('state', 'closed'); + + //preset menu click events + jQuery.fn.clickActions = function(rp, rpPickers, doneBtn){ + + if(jQuery(this).is('.ui-daterangepicker-specificDate')){ + doneBtn.hide(); + rpPickers.show(); + rp.find('.title-start').text( options.presets.specificDate ); + rp.find('.range-start').restoreDateFromData().show(); + rp.find('.range-end').restoreDateFromData().hide(); + setTimeout(function(){doneBtn.fadeIn();}, 400); + } + else if(jQuery(this).is('.ui-daterangepicker-allDatesBefore')){ + doneBtn.hide(); + rpPickers.show(); + rp.find('.title-end').text( options.presets.allDatesBefore ); + rp.find('.range-start').saveDateToData().datepicker('setDate', options.earliestDate).hide(); + rp.find('.range-end').restoreDateFromData().show(); + setTimeout(function(){doneBtn.fadeIn();}, 400); + } + else if(jQuery(this).is('.ui-daterangepicker-allDatesAfter')){ + doneBtn.hide(); + rpPickers.show(); + rp.find('.title-start').text( options.presets.allDatesAfter ); + rp.find('.range-start').restoreDateFromData().show(); + rp.find('.range-end').saveDateToData().datepicker('setDate', options.latestDate).hide(); + setTimeout(function(){doneBtn.fadeIn();}, 400); + } + else if(jQuery(this).is('.ui-daterangepicker-dateRange')){ + doneBtn.hide(); + rpPickers.show(); + rp.find('.title-start').text(options.rangeStartTitle); + rp.find('.title-end').text(options.rangeEndTitle); + rp.find('.range-start').restoreDateFromData().show(); + rp.find('.range-end').restoreDateFromData().show(); + setTimeout(function(){doneBtn.fadeIn();}, 400); + } + else { + //custom date range + doneBtn.hide(); + rp.find('.range-start, .range-end').hide(400, function(){ + rpPickers.hide(); + }); + var dateStart = (typeof jQuery(this).data('dateStart') == 'string') ? Date.parse(jQuery(this).data('dateStart')) : jQuery(this).data('dateStart')(); + var dateEnd = (typeof jQuery(this).data('dateEnd') == 'string') ? Date.parse(jQuery(this).data('dateEnd')) : jQuery(this).data('dateEnd')(); + rp.find('.range-start').datepicker('setDate', dateStart).find('.ui-datepicker-current-day').trigger('click'); + rp.find('.range-end').datepicker('setDate', dateEnd).find('.ui-datepicker-current-day').trigger('click'); + } + + return false; + } + //picker divs + var rpPickers = jQuery('
      Start Date
      End Date
      ').appendTo(rp); + rpPickers.find('.range-start, .range-end').datepicker(options.datepickerOptions); + rpPickers.find('.range-start').datepicker('setDate', inputDateA); + rpPickers.find('.range-end').datepicker('setDate', inputDateB); + var doneBtn = jQuery('') + .click(function(){ + rp.find('.ui-datepicker-current-day').trigger('click'); + hideRP(); + }) + .hover( + function(){ + jQuery(this).addClass('ui-state-hover'); + }, + function(){ + jQuery(this).removeClass('ui-state-hover'); + } + ) + .appendTo(rpPickers); + + + + + //inputs toggle rangepicker visibility + jQuery(this).click(function(){ + toggleRP(); + return false; + }); + //hide em all + rpPickers.css('display', 'none').find('.range-start, .range-end, .btnDone').css('display', 'none'); + + //inject rp + jQuery(options.appendTo).append(rp); + + //wrap and position + rp.wrap('
      '); + + if(options.posX){ + rp.parent().css('left', options.posX); + } + if(options.posY){ + rp.parent().css('top', options.posY); + } + + //add arrows (only available on one input) + if(options.arrows && rangeInput.size()==1){ + var prevLink = jQuery(''+ options.prevLinkText +''); + var nextLink = jQuery(''+ options.nextLinkText +''); + jQuery(this) + .addClass('ui-rangepicker-input ui-widget-content') + .wrap('
      ') + .before( prevLink ) + .before( nextLink ) + .parent().find('a').click(function(){ + var dateA = rpPickers.find('.range-start').datepicker('getDate'); + var dateB = rpPickers.find('.range-end').datepicker('getDate'); + var diff = Math.abs( new TimeSpan(dateA - dateB).getTotalMilliseconds() ) + 86400000; //difference plus one day + + if(jQuery(this).is('.ui-daterangepicker-prev')){ diff = -diff; } + + $('#'+sid).datepicker('setDate', dateA.add({milliseconds: diff})); + $('#'+eid).datepicker('setDate', dateB.add({milliseconds: diff})); + + return false; + }) + .hover( + function(){ + jQuery(this).addClass('ui-state-hover'); + }, + function(){ + jQuery(this).removeClass('ui-state-hover'); + }) + ; + } + + + jQuery(document).click(function(){ + if (rp.is(':visible')) { + hideRP(); + } + }); + + rp.click(function(){return false;}).hide(); + return this; +} + + + + + +/** + * Version: 1.0 Alpha-1 + * Build Date: 13-Nov-2007 + * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved. + * License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. + * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/ + */ +Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}}; +Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;idate)?1:(this=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);} +if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);} +if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);} +if(x.hour||x.hours){this.addHours(x.hour||x.hours);} +if(x.month||x.months){this.addMonths(x.month||x.months);} +if(x.year||x.years){this.addYears(x.year||x.years);} +if(x.day||x.days){this.addDays(x.day||x.days);} +return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(valuemax){throw new RangeError(value+" is not a valid value for "+name+".");} +return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;} +if(!x.second&&x.second!==0){x.second=-1;} +if(!x.minute&&x.minute!==0){x.minute=-1;} +if(!x.hour&&x.hour!==0){x.hour=-1;} +if(!x.day&&x.day!==0){x.day=-1;} +if(!x.month&&x.month!==0){x.month=-1;} +if(!x.year&&x.year!==0){x.year=-1;} +if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());} +if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());} +if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());} +if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());} +if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());} +if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());} +if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());} +if(x.timezone){this.setTimezone(x.timezone);} +if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);} +return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;} +var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}} +return w;};Date.prototype.isDST=function(){return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();}; +Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;} +return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;i t2) ? 1 : 0; + }; + + this.equals = function (time) { + return (this.compareTo(time) === 0); + }; + + this.add = function (time) { + return (time === null) ? this : this.addSeconds(time.getTotalMilliseconds() / 1000); + }; + + this.subtract = function (time) { + return (time === null) ? this : this.addSeconds(-time.getTotalMilliseconds() / 1000); + }; + + this.addDays = function (n) { + return new TimeSpan(this.getTotalMilliseconds() + (n * 86400000)); + }; + + this.addHours = function (n) { + return new TimeSpan(this.getTotalMilliseconds() + (n * 3600000)); + }; + + this.addMinutes = function (n) { + return new TimeSpan(this.getTotalMilliseconds() + (n * 60000)); + }; + + this.addSeconds = function (n) { + return new TimeSpan(this.getTotalMilliseconds() + (n * 1000)); + }; + + this.addMilliseconds = function (n) { + return new TimeSpan(this.getTotalMilliseconds() + n); + }; + + this.get12HourHour = function () { + return (this.getHours() > 12) ? this.getHours() - 12 : (this.getHours() === 0) ? 12 : this.getHours(); + }; + + this.getDesignator = function () { + return (this.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator; + }; + + this.toString = function (format) { + this._toString = function () { + if (this.getDays() !== null && this.getDays() > 0) { + return this.getDays() + "." + this.getHours() + ":" + this.p(this.getMinutes()) + ":" + this.p(this.getSeconds()); + } + else { + return this.getHours() + ":" + this.p(this.getMinutes()) + ":" + this.p(this.getSeconds()); + } + }; + + this.p = function (s) { + return (s.toString().length < 2) ? "0" + s : s; + }; + + var me = this; + + return format ? format.replace(/dd?|HH?|hh?|mm?|ss?|tt?/g, + function (format) { + switch (format) { + case "d": + return me.getDays(); + case "dd": + return me.p(me.getDays()); + case "H": + return me.getHours(); + case "HH": + return me.p(me.getHours()); + case "h": + return me.get12HourHour(); + case "hh": + return me.p(me.get12HourHour()); + case "m": + return me.getMinutes(); + case "mm": + return me.p(me.getMinutes()); + case "s": + return me.getSeconds(); + case "ss": + return me.p(me.getSeconds()); + case "t": + return ((me.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator).substring(0, 1); + case "tt": + return (me.getHours() < 12) ? Date.CultureInfo.amDesignator : Date.CultureInfo.pmDesignator; + } + } + ) : this._toString(); + }; + return this; +}; + +/** + * Gets the time of day for this date instances. + * @return {TimeSpan} TimeSpan + */ +Date.prototype.getTimeOfDay = function () { + return new TimeSpan(0, this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds()); +}; + +/* + * TimePeriod(startDate, endDate); + * TimePeriod(years, months, days, hours, minutes, seconds, milliseconds); + */ +var TimePeriod = function (years, months, days, hours, minutes, seconds, milliseconds) { + var attrs = "years months days hours minutes seconds milliseconds".split(/\s+/); + + var gFn = function (attr) { + return function () { + return this[attr]; + }; + }; + + var sFn = function (attr) { + return function (val) { + this[attr] = val; + return this; + }; + }; + + for (var i = 0; i < attrs.length ; i++) { + var $a = attrs[i], $b = $a.slice(0, 1).toUpperCase() + $a.slice(1); + TimePeriod.prototype[$a] = 0; + TimePeriod.prototype["get" + $b] = gFn($a); + TimePeriod.prototype["set" + $b] = sFn($a); + } + + if (arguments.length == 7) { + this.years = years; + this.months = months; + this.setDays(days); + this.setHours(hours); + this.setMinutes(minutes); + this.setSeconds(seconds); + this.setMilliseconds(milliseconds); + } else if (arguments.length == 2 && arguments[0] instanceof Date && arguments[1] instanceof Date) { + // startDate and endDate as arguments + + var d1 = years.clone(); + var d2 = months.clone(); + + var temp = d1.clone(); + var orient = (d1 > d2) ? -1 : +1; + + this.years = d2.getFullYear() - d1.getFullYear(); + temp.addYears(this.years); + + if (orient == +1) { + if (temp > d2) { + if (this.years !== 0) { + this.years--; + } + } + } else { + if (temp < d2) { + if (this.years !== 0) { + this.years++; + } + } + } + + d1.addYears(this.years); + + if (orient == +1) { + while (d1 < d2 && d1.clone().addDays(Date.getDaysInMonth(d1.getYear(), d1.getMonth()) ) < d2) { + d1.addMonths(1); + this.months++; + } + } + else { + while (d1 > d2 && d1.clone().addDays(-d1.getDaysInMonth()) > d2) { + d1.addMonths(-1); + this.months--; + } + } + + var diff = d2 - d1; + + if (diff !== 0) { + var ts = new TimeSpan(diff); + this.setDays(ts.getDays()); + this.setHours(ts.getHours()); + this.setMinutes(ts.getMinutes()); + this.setSeconds(ts.getSeconds()); + this.setMilliseconds(ts.getMilliseconds()); + } + } + return this; +}; diff --git a/widgets/EDateRangePicker/assets/daterange/ui.daterangepicker.css b/widgets/EDateRangePicker/assets/daterange/ui.daterangepicker.css new file mode 100755 index 0000000..6c71e83 --- /dev/null +++ b/widgets/EDateRangePicker/assets/daterange/ui.daterangepicker.css @@ -0,0 +1,103 @@ +/*styles for jquery ui daterangepicker plugin */ + +.ui-daterangepickercontain { + position: absolute; + z-index: 999; +} +.ui-daterangepickercontain .ui-daterangepicker { + float: left; + padding: 5px !important; + width: auto; + display: inline; + background-image: none !important; + clear: left; +} +.ui-daterangepicker ul, .ui-daterangepicker .ranges, .ui-daterangepicker .range-start, .ui-daterangepicker .range-end { + float: left; + padding: 0; + margin: 0; +} +.ui-daterangepicker .ranges { + width: auto; + position: relative; + padding: 5px 5px 40px 0; + margin-left: 10px; +} +.ui-daterangepicker .range-start, .ui-daterangepicker .range-end { + margin-left: 5px; +} +.ui-daterangepicker button.btnDone { + margin: 0 5px 5px 0; + position: absolute; + bottom: 0; + right: 0; + clear: both; + cursor: pointer; + font-size: 1.1em; +} +.ui-daterangepicker ul { + width: 17.6em; + background: none; + border: 0; +} +.ui-daterangepicker li { + list-style: none; + padding: 1px; + cursor: pointer; + margin: 1px 0; +} +.ui-daterangepicker li.ui-state-hover, .ui-daterangepicker li.ui-state-active { + padding: 0; +} +.ui-daterangepicker li.preset_0 { + margin-top: 1.5em !important; +} +.ui-daterangepicker .ui-widget-content a { + text-decoration: none !important; +} +.ui-daterangepicker li a { + font-weight: normal; + margin: .3em .5em; + display: block; +} +.ui-daterangepicker li span { + float: right; + margin: .3em .2em; +} +.ui-daterangepicker .title-start, .ui-daterangepicker .title-end { + display: block; + margin: 0 0 .2em; + font-size: 1em; + padding: 0 4px 2px; +} +.ui-daterangepicker .ui-datepicker-inline { + font-size: 1em; +} +.ui-daterangepicker-arrows { + padding: 2px; + width: 204px; + position: relative; +} +.ui-daterangepicker-arrows input.ui-rangepicker-input { + width: 158px; + margin: 0 2px 0 20px; + padding: 2px; + height: 1.1em; +} +.ui-daterangepicker-arrows .ui-daterangepicker-prev, .ui-daterangepicker-arrows .ui-daterangepicker-next { + position: absolute; + top: 2px; + padding: 1px; +} +.ui-daterangepicker-arrows .ui-daterangepicker-prev { + left: 2px; +} +.ui-daterangepicker-arrows .ui-daterangepicker-next { + right: 2px; +} +.ui-daterangepicker-arrows .ui-daterangepicker-prev:hover, +.ui-daterangepicker-arrows .ui-daterangepicker-next:hover, +.ui-daterangepicker-arrows .ui-daterangepicker-prev:focus, +.ui-daterangepicker-arrows .ui-daterangepicker-next:focus { + padding: 0; +} diff --git a/widgets/jqPrettyPhoto/jqPrettyPhoto.php b/widgets/jqPrettyPhoto/jqPrettyPhoto.php new file mode 100644 index 0000000..d5c08d6 --- /dev/null +++ b/widgets/jqPrettyPhoto/jqPrettyPhoto.php @@ -0,0 +1,42 @@ +clientScript; + $cs->registerCoreScript('jquery'); + $assets = Yii::app()->extensionPath. DIRECTORY_SEPARATOR.'prettyPhoto'.DIRECTORY_SEPARATOR; + $aUrl = Yii::app()->getAssetManager()->publish($assets); + $cs->registerScriptFile($aUrl.'/'.self::scriptName()); + $cs->registerCssFile($aUrl .self::scriptName(true)); + } + + public static function addPretty($jsSelector=".gallery a", $gallery=self::PRETTY_GALLERY, $theme=self::THEME_FACEBOOK, $opts=array()){ + + self::registerScript(); + + $opts['theme']=$theme; + + + Yii::app()->clientScript->registerScript(__CLASS__,' + $("'.$jsSelector.'").attr("rel","prettyPhoto'.($gallery==self::PRETTY_GALLERY?'['.time().']':'').'"); + $("a[rel^=\'prettyPhoto\']").prettyPhoto('.CJavaScript::encode($opts).'); + ',CClientScript::POS_READY); + } + + +} diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnNext.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnNext.png new file mode 100755 index 0000000000000000000000000000000000000000..b28c1ef3d595d5af9db1f2a4378cfd64407ed5c0 GIT binary patch literal 1411 zcmV-}1$_F6P)$9a05|MJUF0->4SGK<7F5TJ=lWKN6+&IhzfIXTxP_BAUYSlC~k)ya96~A ztE!1Fs3@{uSCwU7suGg-Dqm*4{6%~bnXhbax0`G>8;y^TlaJ!y=Z$>*$bCxrnvF(o z`x+M)R}d8y<%*4sb=vKA7v=~-N$_0n-ZRLqHxCaF-bMz>;maqN%k>M_92kbLQc)rz zB8pyLU&UAqOwrNN6cZCek&%%UV)*w#qa-FKlGEv|W5{3m+Nvr57=wC&(2ff5mzNh( z17JKGEL7k>2JHbJV~7v<&GHpkvnUX*@G!g&=-b;{j2C0X$H#{?xZRXAJPcs=9iF5m z$;ezRVzj2FM&MRfRz${TF_i##NF31cG^#Z`@cilNNv;SVRNO-;YUtmzPp;pO=?M$H&KXa&kgtWo0s6lPBgmI5?pE{Cs(iF?a`l;~DyD zeo-f&{o>+6-iK$tg6sHliqT3V>L zx0mkj?xbHsLxUI=K#h%!R99C=xw*MwjGUYtIy^k2!oou8>FJR^X=!QH*48EpNJ&YN zz7-V})Y{rg#l^)k2J7^-2D?WJrFf_^uu26000%G@R$X09v$M0`^;N^cP^fA+Q(jhM zR905f>guZ8WBY@$Q1eGn3BF&S-6IO?Hdx z>uV}0DWR^eF6oE&U>w5J%A>2v)`ghfQje?W0Dg6KC9&Gv+$?R{&z#uwe4u1DKR++| z0`pl5JPg3TzCNn2uNR5JkaKf$B7Z_1&}6(^FbrUZ(Z+b=uwC z6(A&3NPBN@PmI#h(ILt+GyQw^>CHgU3XH4^;Dv>Sz>|8Qt(jZ55^YOLN}|opO-W|v z)`SB2*4EZaaw;z`7Z10$w~KTUvj`0&EQiD4JrpU!n-28IYLy5594xT8W%)a>_yR`3 z4uAv%?*STd2moYs%#XI6ogI-V+As$Uv9+}&&z6>!Bm^*iV`GB~3JU1x=tz`+@o0Yw z+J^WV9UU#@iT~smWm{7`EZfx7BohS#jDGsnrH2m_UXQDI4>Bu&@Eaf)XZ|(~k9jZ_ z_ZWxq`emr|Xzc63$X|%rJll+IY;0_V?|(O6W`YCeEnxjf(UTDFL-@J*W=y-;YJV`; zte&CGf|v^iSUrL(V1rF)A^YGM9v(j6)c=JS`mZnWV1WUv&|&|TH8L{d9vmF}hxfbR z82m3^-_rhKSS4+OhpU&ZpOyf34Gj(b&6|EdCyLJu@m1w&8bCiKt?{ei;p}BIk$v;W z;K0BD2qXDg#@j>#gM45R2lxBQbC~&$0rR=di9ZMN#~@dC2t52le+>RFzyR~BA^19M RLBjw5002ovPDHLkV1n=}nO*<@ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnPrevious.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/btnPrevious.png new file mode 100755 index 0000000000000000000000000000000000000000..e0cd9c49af7fe2f6ab694843bdd3b90ce9217cb3 GIT binary patch literal 1442 zcmV;T1zq}yP)DPR}?-yIFE=Eh>D`1 zxDZ4LPAETaM%Rg1tJM?Bq#w*j8W7$G%8Lw&qL>a zCw-c0=k{xpz@y)ROWmrvb?SWQJHxHA4-E}n(SMgP*YTP@kUTy+|!o71v*0Uj9&rU)Awvb?t|yrluuz?lkGJ^!4>Q=_3#Yh7?%3ySpO=mYhx}U&DY{ ztyUfuiv?p_UHazs_V(+RmX<#~!COX0NBtf&z+PNj(ACuyg9R|V-7WxmjW|KE>|Uds z@STc?cg@YsKf-UK&-yU{K0Q69%gakX4+sdLz`#JA)xX(ON1M&Y_pk=Ql>x3!!mdfe z!^0^!IG908e&ZRJBWj|fqrXs78-l{_=_v@jjIfe|ffq^G2m?4m^pL_e6dJiF1qN%N zN7iBv{W#*>#30@cz{pcPr!(of7J!S2ium5d#02Y>wRk`5Q93?XY=7I{-o9Wrc}-P4 zijR+f<+;7RJ8Nysxo|~Jaii!%Fot>rm`FYk+ zS69dHT3lRYgFQtzUOxh`I6)W|z%w&5?&_^S2ghNqocjra(~1+%0Vc$oy}iBE*4D=J zf`S5W8<5G#N$Tn8;ro!p#6)%$WMgB4Iy*brNyEd#%-Ht!Hs$8#((&;zr$TIOEG;Z7 z@LUWu*&qh%L!_jnsm&{8?^!)!Wk?@MMb%{ePRf%0|=@*@(HY=il?Tga{EH4qxvIv(ca)R=-l7m z=dj0mge~-X6I1W@6l_i33LLtW&2z{I<5w2)1HBr?f(#o-m~a)XCyiluc>E>604H)GzA+XomH+?%07*qoM6N<$f}$awVgLXD literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/contentPattern.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/contentPattern.png new file mode 100755 index 0000000000000000000000000000000000000000..e5a047c3a7efb0f8085e0b70523299af00a5dbd6 GIT binary patch literal 130 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!fo6;Bt(kcwMLDG3QT&O5jsIO33Mko90i<6^cxDT}NJ0fqt! a2NIKlt||NsBb%*?E=uIA+AG%zrD`0!y=R1_N<+vm@pjg5^ZBqW4{ zgcKDO=ggUN^5n@6A3hv7aNygwZyp{VGiT1cdi82iQIVXS9M~i*gyK&Yu&54*1lh^J znxLT2my$UzW7WEx*ZT@|&X?rguUPlK=KX)mqpTdl9&9X0-U1vFe5XV=OIC<1TA^wB zAR@v0R&G+mkFG;kP93Sa$EGmNNLpZ-!L!AgPrAh9_7P-L(M E0LAB0sQ>@~ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/loader.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..50820eedd904647a76c4c2c1d830d4339588c08d GIT binary patch literal 2545 zcma*pYg7~I83*u}xzA)~n25ScZ5t-T5>nU=30MS$Xhdjjt5TMMW#9(@)N1wHw{N#>*%A~K6dfI%mzSs0>1;MzQc{x1WQvWAEi5cd zO-&6A4ejdcYHV!G&dwej90WgkdA*fGvWCM(P; z$dsjIrxfkWO-cJGD`!tG@Op!gxcHQxYYW#Q0eW60s};c7^ldElX7vkaqh-b(7r4^0 z+UAb0ZSr75<*E5h0II=##CBs?6qZq|wUb!3#%m$M1s7pTB@&S06@9Ay z-h5f_b9-#xd~c$}>EX+VI*I%()}0TCh#*z{qN+;rk#%*HM(Q=fSuu34zchyM3e)?`6^f~$^~ z0L(`R@{+Sb&Lw^rQ0mE&rsN{19?LGi8VEttI=`{N-icg!PPcINvze}`ss#~~E`&+u zLbM5e>i*sa;(5N^{?)@C{Y}}!YN8g;6QZ;cANQNPJ-Nt=h#5XnucjTE3{bk)~0FmXzWdpB5>Zy)m`E2tm_CcnpIfqNU+@bKQydj=IwV zTyutJD5yo*h7Dj4WQ}P<9L%LeFnacSCsO3N5pIvX4hH0%iP-$*y7rv*%*Qc)j;EmW z-mi|`{kHYCazV_H3nL>$47PQ#5YXQnr$}wI&wcr@=kZ1#eg!+#&@)Bc1U9(*cd`I# z%)^=g#F56s=CvHdJ@QPLEeg)N^*V4>aPW!^n{-oJw+~n{T?uQnOqQ|67o&Wra3kj~ zmAZ3|x(4m>=2n}t+vbOyw4bh&GstNhHdGB?2pRx>gF^$ChIy<0=d!7qV$XlP6VHk`U}S=M5)om<(f8q3iQk zYG|Bgahee;Ls{S0WIffD*~Y^)CSUQw@&j#Wd|^??$zfXtW(Iw7j+Mq=5o)@5yFgtY z3d6oYdc1APw%)G~Jcuq~0;(?G6F#?AES#&0E{T>)Z(k{t*WY&5I+E?QMtN(E4RVvz zdn~6azL}H7T6ge<>4C;SBcmS4vGe}t%wNr?EeYZhrui~;S7~?-@1Vh(fyEDV=&7N3M|fReKPNa}sSNhdVs+rpOitDW8pX6l>^lDsUvo$c$X?+;-k zG}O%~)wtgxWGV2;RxP_8XXXx8BXs$Cg*s|OzZ>l4@i1EsFPWdKUR<%xPVd;b{N)E( z$%oEzXyux%p7u8;eZu-R#`m3yF=G{BRn@k#=4Ph;;2|T5OA@2^m%^fO2fwRgw@R{( zIO9xB;5jah#gmjV0q*WR$wrlG*GZ+}?36LEVopx8&UoR5|FP2X(gg*>F2Q`DOOWzT zKy$O1#JGKIqQfYwpC&#w#q@DPqW?2e#VL|C9rwWEFAWIx{OQyh2QiYjN9*YZVSoMS zFA84%7}x9g)1Aex=0uh3IjS_3TZ$cycqP0)!%# zOBK66#7X}`IVZ(={4$W>L=h`Z1T4#AR|_aHZ=cBtyvh4h;coJH!AS0chIeuLuVE;J zMU;zXpKv4ew*ej2mcLgY77c0rcaD7i-OHBM_w!bI7sLh;$kU&9zltxt1OD2qL|J;&bhtXwqAHM6xRf*yD4ofPQO{_whBa&Y*iCFs_G=IgnfD{l3X!rx368LCX1Vtw#^VR1=jt|2}Q-YZ8)R$_o? zXe&>PWnSYgc3wT8Sa({7?q-O!oyI_c#1%&XjP`}73A_%VXfsOf$8jS`uD`Yq!wLCq zId#TxPJTy@wTF!w78H#BZ=C+$RSaM?39k!c*vN7#tXBU3;O}dHT^_;L4G+E)eAx!e z$jjz9$B$QQ^7DdOx|rFAG2&g6TUH)%6sqBDIU>PP0|HTYo)Bxj(VK#x1f+}5(H#tw zbb4iqfOZ2a4yFe~)DXD?&}9Ik^#%!PL|Z?cDZK2Tl4%H^Jh*rZTQDq<@>&LjL8>&bmbP;{9$V1f72|-IC6J*aS0@jCMCW#2N6=@ z4`AZpxsaAsVQ@O2`+(k0w?jjrokZ16XhMUc+v!dQ8ajnupX>w}bRm9Xw)Di6(Qq#i K>KdTlt$zd2^$V2% literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/sprite.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_rounded/sprite.png new file mode 100755 index 0000000000000000000000000000000000000000..fb8c0f83d715aec1014b77f0b44002a07a635052 GIT binary patch literal 4076 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 RLBjw5002ovPDHLkV1n=}nO*<@ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/btnPrevious.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/btnPrevious.png new file mode 100755 index 0000000000000000000000000000000000000000..e0cd9c49af7fe2f6ab694843bdd3b90ce9217cb3 GIT binary patch literal 1442 zcmV;T1zq}yP)DPR}?-yIFE=Eh>D`1 zxDZ4LPAETaM%Rg1tJM?Bq#w*j8W7$G%8Lw&qL>a zCw-c0=k{xpz@y)ROWmrvb?SWQJHxHA4-E}n(SMgP*YTP@kUTy+|!o71v*0Uj9&rU)Awvb?t|yrluuz?lkGJ^!4>Q=_3#Yh7?%3ySpO=mYhx}U&DY{ ztyUfuiv?p_UHazs_V(+RmX<#~!COX0NBtf&z+PNj(ACuyg9R|V-7WxmjW|KE>|Uds z@STc?cg@YsKf-UK&-yU{K0Q69%gakX4+sdLz`#JA)xX(ON1M&Y_pk=Ql>x3!!mdfe z!^0^!IG908e&ZRJBWj|fqrXs78-l{_=_v@jjIfe|ffq^G2m?4m^pL_e6dJiF1qN%N zN7iBv{W#*>#30@cz{pcPr!(of7J!S2ium5d#02Y>wRk`5Q93?XY=7I{-o9Wrc}-P4 zijR+f<+;7RJ8Nysxo|~Jaii!%Fot>rm`FYk+ zS69dHT3lRYgFQtzUOxh`I6)W|z%w&5?&_^S2ghNqocjra(~1+%0Vc$oy}iBE*4D=J zf`S5W8<5G#N$Tn8;ro!p#6)%$WMgB4Iy*brNyEd#%-Ht!Hs$8#((&;zr$TIOEG;Z7 z@LUWu*&qh%L!_jnsm&{8?^!)!Wk?@MMb%{ePRf%0|=@*@(HY=il?Tga{EH4qxvIv(ca)R=-l7m z=dj0mge~-X6I1W@6l_i33LLtW&2z{I<5w2)1HBr?f(#o-m~a)XCyiluc>E>604H)GzA+XomH+?%07*qoM6N<$f}$awVgLXD literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/contentPattern.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/contentPattern.png new file mode 100755 index 0000000000000000000000000000000000000000..7b50aff880e57ea386400d763dbddf82fff72be6 GIT binary patch literal 121 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4NtU=qlmzFem6RtIr7}3CIKlt||NsBb%*?E=uIA+AG%zrD`0!y=R1_N<+vm@pjg5^ZBqW4{ zgcKDO=ggUN^5n@6A3hv7aNygwZyp{VGiT1cdi82iQIVXS9M~i*gyK&Yu&54*1lh^J znxLT2my$UzW7WEx*ZT@|&X?rguUPlK=KX)mqpTdl9&9X0-U1vFe5XV=OIC<1TA^wB zAR@v0R&G+mkFG;kP93Sa$EGmNNLpZ-!L!AgPrAh9_7P-L(M E0LAB0sQ>@~ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/loader.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..50820eedd904647a76c4c2c1d830d4339588c08d GIT binary patch literal 2545 zcma*pYg7~I83*u}xzA)~n25ScZ5t-T5>nU=30MS$Xhdjjt5TMMW#9(@)N1wHw{N#>*%A~K6dfI%mzSs0>1;MzQc{x1WQvWAEi5cd zO-&6A4ejdcYHV!G&dwej90WgkdA*fGvWCM(P; z$dsjIrxfkWO-cJGD`!tG@Op!gxcHQxYYW#Q0eW60s};c7^ldElX7vkaqh-b(7r4^0 z+UAb0ZSr75<*E5h0II=##CBs?6qZq|wUb!3#%m$M1s7pTB@&S06@9Ay z-h5f_b9-#xd~c$}>EX+VI*I%()}0TCh#*z{qN+;rk#%*HM(Q=fSuu34zchyM3e)?`6^f~$^~ z0L(`R@{+Sb&Lw^rQ0mE&rsN{19?LGi8VEttI=`{N-icg!PPcINvze}`ss#~~E`&+u zLbM5e>i*sa;(5N^{?)@C{Y}}!YN8g;6QZ;cANQNPJ-Nt=h#5XnucjTE3{bk)~0FmXzWdpB5>Zy)m`E2tm_CcnpIfqNU+@bKQydj=IwV zTyutJD5yo*h7Dj4WQ}P<9L%LeFnacSCsO3N5pIvX4hH0%iP-$*y7rv*%*Qc)j;EmW z-mi|`{kHYCazV_H3nL>$47PQ#5YXQnr$}wI&wcr@=kZ1#eg!+#&@)Bc1U9(*cd`I# z%)^=g#F56s=CvHdJ@QPLEeg)N^*V4>aPW!^n{-oJw+~n{T?uQnOqQ|67o&Wra3kj~ zmAZ3|x(4m>=2n}t+vbOyw4bh&GstNhHdGB?2pRx>gF^$ChIy<0=d!7qV$XlP6VHk`U}S=M5)om<(f8q3iQk zYG|Bgahee;Ls{S0WIffD*~Y^)CSUQw@&j#Wd|^??$zfXtW(Iw7j+Mq=5o)@5yFgtY z3d6oYdc1APw%)G~Jcuq~0;(?G6F#?AES#&0E{T>)Z(k{t*WY&5I+E?QMtN(E4RVvz zdn~6azL}H7T6ge<>4C;SBcmS4vGe}t%wNr?EeYZhrui~;S7~?-@1Vh(fyEDV=&7N3M|fReKPNa}sSNhdVs+rpOitDW8pX6l>^lDsUvo$c$X?+;-k zG}O%~)wtgxWGV2;RxP_8XXXx8BXs$Cg*s|OzZ>l4@i1EsFPWdKUR<%xPVd;b{N)E( z$%oEzXyux%p7u8;eZu-R#`m3yF=G{BRn@k#=4Ph;;2|T5OA@2^m%^fO2fwRgw@R{( zIO9xB;5jah#gmjV0q*WR$wrlG*GZ+}?36LEVopx8&UoR5|FP2X(gg*>F2Q`DOOWzT zKy$O1#JGKIqQfYwpC&#w#q@DPqW?2e#VL|C9rwWEFAWIx{OQyh2QiYjN9*YZVSoMS zFA84%7}x9g)1Aex=0uh3IjS_3TZ$cycqP0)!%# zOBK66#7X}`IVZ(={4$W>L=h`Z1T4#AR|_aHZ=cBtyvh4h;coJH!AS0chIeuLuVE;J zMU;zXpKv4ew*ej2mcLgY77c0rcaD7i-OHBM_w!bI7sLh;$kU&9zltxt1OD2qL|J;&bhtXwqAHM6xRf*yD4ofPQO{_whBa&Y*iCFs_G=IgnfD{l3X!rx368LCX1Vtw#^VR1=jt|2}Q-YZ8)R$_o? zXe&>PWnSYgc3wT8Sa({7?q-O!oyI_c#1%&XjP`}73A_%VXfsOf$8jS`uD`Yq!wLCq zId#TxPJTy@wTF!w78H#BZ=C+$RSaM?39k!c*vN7#tXBU3;O}dHT^_;L4G+E)eAx!e z$jjz9$B$QQ^7DdOx|rFAG2&g6TUH)%6sqBDIU>PP0|HTYo)Bxj(VK#x1f+}5(H#tw zbb4iqfOZ2a4yFe~)DXD?&}9Ik^#%!PL|Z?cDZK2Tl4%H^Jh*rZTQDq<@>&LjL8>&bmbP;{9$V1f72|-IC6J*aS0@jCMCW#2N6=@ z4`AZpxsaAsVQ@O2`+(k0w?jjrokZ16XhMUc+v!dQ8ajnupX>w}bRm9Xw)Di6(Qq#i K>KdTlt$zd2^$V2% literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/sprite.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/dark_square/sprite.png new file mode 100755 index 0000000000000000000000000000000000000000..4fe354752455e070ebb56f42a60fc2233de45bd7 GIT binary patch literal 3507 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 literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnNext.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnNext.png new file mode 100755 index 0000000000000000000000000000000000000000..e809c3b64219468c4c744a4d4f086460ee6c8d6f GIT binary patch literal 845 zcmV-T1G4;yP)*<*4T)FKQHep0ar5R4IgX^MaUqaei9+gx zK#oWha#RSUqSL<~3WaX<08-Gx({VYS&ijjti?_uR@~-8hZnyh^-|xRkG^9)>qkKN! zl-KJu5DVD?g=ju>xm?$Xh5WNRfWqOh&EardB^DA(E|(8RqtR)b&DNLlZLpn(p$eGi z`&orTF|@qAJk2o7i&QGLOI##dQYw|htE;OsCX;E5SV%0DN;Q&9CZ9}9OsI*4#8Ru( zs`d5t$3~-Zgjh%{jYc!Jv9U2@Fc^l3g~ZZowHMRr^dp^4H$*HXmR_&dE-fvYh=wc_ z3Pro!zDX=(u~^)J?Id2QRE~*+q-lEBVzIU=>}-WHNh1mRj|C z{RryGSG}%2dIs5OG>&IyXXA-Pf+n(PJ^^_=p4YKhtVrbXN3+>v=jP@Tkw~P_@2sjT zkSxnK1A)L=Fc{1u5vVJW3kwTxA&MW7NQ7^ieW(jp1wGI&cw?UNjUh#S6ZTw zv=GQ`i9&7*fyB)~dlCbzaP#I3d4f*+W2B^a+`I*CAvtuW2}tDH;^r%83popX4~!3$ z21E#lBbNccM_ivBiM@4iz~BDKto|Fa71itLv{we-TU_n~*cW_|z`dHEQ5#*7zXcco X7c=PvI!UA-00000NkvXXu0mjfDKUXP literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnPrevious.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/btnPrevious.png new file mode 100755 index 0000000000000000000000000000000000000000..0812542cccfdfd9d1644ed721516c00fc6a03c24 GIT binary patch literal 828 zcmV-C1H=4@P)-u>SFdGE)KMkDk;;yCW4W(C*+z6Lgc zFY;|dX$bxTZi7F9Ykb?Ev7Z9+0(b#Di=?A%@H+V0SwP+b-vz6X^fUlhz#FH5w1aOW z>FYhXjK+9>tc(C6K29qsq!R2vYzeYKq^0;HM*iEn3dyhunl7I|_Lf8F%zmrz~qC>8(u37?tz=A@d z;A9Eu^ZBm8EZ(OHC@SRA($eKGUEgAT#MYKR>U_=krgrTCLzGCqW>sR_j!;SbU_@ z>9k_30n{Na7RzL*R9Z9`40^Fva;lIflj&T!Tz+UY8fV1ZkEuc?lS!M|Y!>~BMitWI z@l@*ddPCNbolfTfwv9}q(b$$PBv!N8+;=z}nO3W{BU?zUTCMgIKwq}o?OoYIVpXfv zzTIwr-tBh3%N7!Ab91xja=BjgdcD4o;|hL2|JNaXZ*Om3tyYI+an&jm3OjJ0e1)N8 zm#)W^%jH_j%gdR;U~nK?NUT&U)mT|s$sU4a3yBqv$LqmhFgF|yN3w;)T3cJIg~Q=> zSOkYO`kz1&5zOWX$XY6pBwI)T3Gl0cf_{;tYag+xA0&`1Mj=}ykXwvGZjnIZVxV2d z06REt5=gvZR~UnQgX1RMC#&dK5s<{S!N)sRu@ZhTcfdAuRQ4h@;f#9=euwxvJ2}?Y zy#{|B_b~9cW8qbFRO5p`9$yjx>@&E_r{*VgLY3ri0R{lIBCT m-o+D*vL38xWMObn^tomvpcVhX~C5P hfz0h)jSP%z43bA#R32EIs{v|b@O1TaS?83{1OPdrCnx{_ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternRight.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/contentPatternRight.png new file mode 100755 index 0000000000000000000000000000000000000000..76e50d0f5c6a8d0ee5f69b82493d94805f93d47b GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE;=WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!foEl(H6kcwMLfB0E=SQwO*la-k#HgqC} m{yP{*X)b2tGiPRG;$g@#WM60<^5zjxFN3G6pUXO@geCwXd?|PU literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/default_thumbnail.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/default_thumbnail.gif new file mode 100755 index 0000000000000000000000000000000000000000..2b1280f32756805aaa557cea32c70b05a2aa46b4 GIT binary patch literal 227 zcmZ?wbhEHbG-6O>IKlt||NsBb%*?E=uIA+AG%zrD`0!y=R1_N<+vm@pjg5^ZBqW4{ zgcKDO=ggUN^5n@6A3hv7aNygwZyp{VGiT1cdi82iQIVXS9M~i*gyK&Yu&54*1lh^J znxLT2my$UzW7WEx*ZT@|&X?rguUPlK=KX)mqpTdl9&9X0-U1vFe5XV=OIC<1TA^wB zAR@v0R&G+mkFG;kP93Sa$EGmNNLpZ-!L!AgPrAh9_7P-L(M E0LAB0sQ>@~ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/loader.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/facebook/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..7ac990cf04f2323f9723518bfd72ce102222cefa GIT binary patch literal 2545 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 ZU;uc#vp>^SEa3nE002ovPDHLkV1iA65^4Yd literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnNext.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnNext.png new file mode 100755 index 0000000000000000000000000000000000000000..b28c1ef3d595d5af9db1f2a4378cfd64407ed5c0 GIT binary patch literal 1411 zcmV-}1$_F6P)$9a05|MJUF0->4SGK<7F5TJ=lWKN6+&IhzfIXTxP_BAUYSlC~k)ya96~A ztE!1Fs3@{uSCwU7suGg-Dqm*4{6%~bnXhbax0`G>8;y^TlaJ!y=Z$>*$bCxrnvF(o z`x+M)R}d8y<%*4sb=vKA7v=~-N$_0n-ZRLqHxCaF-bMz>;maqN%k>M_92kbLQc)rz zB8pyLU&UAqOwrNN6cZCek&%%UV)*w#qa-FKlGEv|W5{3m+Nvr57=wC&(2ff5mzNh( z17JKGEL7k>2JHbJV~7v<&GHpkvnUX*@G!g&=-b;{j2C0X$H#{?xZRXAJPcs=9iF5m z$;ezRVzj2FM&MRfRz${TF_i##NF31cG^#Z`@cilNNv;SVRNO-;YUtmzPp;pO=?M$H&KXa&kgtWo0s6lPBgmI5?pE{Cs(iF?a`l;~DyD zeo-f&{o>+6-iK$tg6sHliqT3V>L zx0mkj?xbHsLxUI=K#h%!R99C=xw*MwjGUYtIy^k2!oou8>FJR^X=!QH*48EpNJ&YN zz7-V})Y{rg#l^)k2J7^-2D?WJrFf_^uu26000%G@R$X09v$M0`^;N^cP^fA+Q(jhM zR905f>guZ8WBY@$Q1eGn3BF&S-6IO?Hdx z>uV}0DWR^eF6oE&U>w5J%A>2v)`ghfQje?W0Dg6KC9&Gv+$?R{&z#uwe4u1DKR++| z0`pl5JPg3TzCNn2uNR5JkaKf$B7Z_1&}6(^FbrUZ(Z+b=uwC z6(A&3NPBN@PmI#h(ILt+GyQw^>CHgU3XH4^;Dv>Sz>|8Qt(jZ55^YOLN}|opO-W|v z)`SB2*4EZaaw;z`7Z10$w~KTUvj`0&EQiD4JrpU!n-28IYLy5594xT8W%)a>_yR`3 z4uAv%?*STd2moYs%#XI6ogI-V+As$Uv9+}&&z6>!Bm^*iV`GB~3JU1x=tz`+@o0Yw z+J^WV9UU#@iT~smWm{7`EZfx7BohS#jDGsnrH2m_UXQDI4>Bu&@Eaf)XZ|(~k9jZ_ z_ZWxq`emr|Xzc63$X|%rJll+IY;0_V?|(O6W`YCeEnxjf(UTDFL-@J*W=y-;YJV`; zte&CGf|v^iSUrL(V1rF)A^YGM9v(j6)c=JS`mZnWV1WUv&|&|TH8L{d9vmF}hxfbR z82m3^-_rhKSS4+OhpU&ZpOyf34Gj(b&6|EdCyLJu@m1w&8bCiKt?{ei;p}BIk$v;W z;K0BD2qXDg#@j>#gM45R2lxBQbC~&$0rR=di9ZMN#~@dC2t52le+>RFzyR~BA^19M RLBjw5002ovPDHLkV1n=}nO*<@ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnPrevious.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/btnPrevious.png new file mode 100755 index 0000000000000000000000000000000000000000..e0cd9c49af7fe2f6ab694843bdd3b90ce9217cb3 GIT binary patch literal 1442 zcmV;T1zq}yP)DPR}?-yIFE=Eh>D`1 zxDZ4LPAETaM%Rg1tJM?Bq#w*j8W7$G%8Lw&qL>a zCw-c0=k{xpz@y)ROWmrvb?SWQJHxHA4-E}n(SMgP*YTP@kUTy+|!o71v*0Uj9&rU)Awvb?t|yrluuz?lkGJ^!4>Q=_3#Yh7?%3ySpO=mYhx}U&DY{ ztyUfuiv?p_UHazs_V(+RmX<#~!COX0NBtf&z+PNj(ACuyg9R|V-7WxmjW|KE>|Uds z@STc?cg@YsKf-UK&-yU{K0Q69%gakX4+sdLz`#JA)xX(ON1M&Y_pk=Ql>x3!!mdfe z!^0^!IG908e&ZRJBWj|fqrXs78-l{_=_v@jjIfe|ffq^G2m?4m^pL_e6dJiF1qN%N zN7iBv{W#*>#30@cz{pcPr!(of7J!S2ium5d#02Y>wRk`5Q93?XY=7I{-o9Wrc}-P4 zijR+f<+;7RJ8Nysxo|~Jaii!%Fot>rm`FYk+ zS69dHT3lRYgFQtzUOxh`I6)W|z%w&5?&_^S2ghNqocjra(~1+%0Vc$oy}iBE*4D=J zf`S5W8<5G#N$Tn8;ro!p#6)%$WMgB4Iy*brNyEd#%-Ht!Hs$8#((&;zr$TIOEG;Z7 z@LUWu*&qh%L!_jnsm&{8?^!)!Wk?@MMb%{ePRf%0|=@*@(HY=il?Tga{EH4qxvIv(ca)R=-l7m z=dj0mge~-X6I1W@6l_i33LLtW&2z{I<5w2)1HBr?f(#o-m~a)XCyiluc>E>604H)GzA+XomH+?%07*qoM6N<$f}$awVgLXD literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/default_thumbnail.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/default_thumbnail.gif new file mode 100755 index 0000000000000000000000000000000000000000..2b1280f32756805aaa557cea32c70b05a2aa46b4 GIT binary patch literal 227 zcmZ?wbhEHbG-6O>IKlt||NsBb%*?E=uIA+AG%zrD`0!y=R1_N<+vm@pjg5^ZBqW4{ zgcKDO=ggUN^5n@6A3hv7aNygwZyp{VGiT1cdi82iQIVXS9M~i*gyK&Yu&54*1lh^J znxLT2my$UzW7WEx*ZT@|&X?rguUPlK=KX)mqpTdl9&9X0-U1vFe5XV=OIC<1TA^wB zAR@v0R&G+mkFG;kP93Sa$EGmNNLpZ-!L!AgPrAh9_7P-L(M E0LAB0sQ>@~ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/loader.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_rounded/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..7ac990cf04f2323f9723518bfd72ce102222cefa GIT binary patch literal 2545 zcma*pYg7~I83*u}xzA)~n25ScX&WZP5>nU=1T3OL6cUK&3gY5=2L-vPKosR#4J1Gy z2{*Y2f`*F<925Z+DqcWPab1;V*Q&?%tge1gx39sBO@dC?%nI@=>b1`dA^f;rd<;kzBEP~@QDg`5nuHDmwWi`ccB(8fJu!D@W@6GOX&Kuyf#(~9#Kot4om)5$3D8qAS)~A$Ca-6yH;bP-YR%)e z*uVwm#a35*X`LHGj32c;jsl4QZfgsvW!V?{2-SqnJnoXXF*nkrHs1S%=LGrPst~{V zbIXcXr&l++uUTGfQbH(6qL7Kn#+Xt|g{`v4+z9V)In}XN;&Aif7dZ&S7naR;h=?Fn^{lc|@~&lZlv?W9&si{Zho3Zq zBNC0W8pT9f=}<$GQHasxVL1urLe*G$-i1I28VmOw4D1-nlxMUF7p{*V9W9#? zG3-PbWllts&>7IxQA0d2*lgcSw`;G+rpt*+JWGhuM10z1YIElzOCrY2>B0NtO_MPS zMFf29+_^(8vP$B!PKEl9XU62S@@i=sRcJ^lp7MFFlGzbm>4Ok7O@u`=7$RC~4%Sy4 zYHq1ICcqWPd4_@-lucMS20@nSCdAI1T?+2+dcgowe`%@+K-I${;uYHgEWPq*J&<;{Drqc!cL#1&wL zi~k_=rv}{|hWqv;9yTrI81Aln{6ubW*40;m3xk6_R;~^o)wq1blF3R~tzpvi6+Rf{ zO@-+>SE~;p9EM~ zU%BS@TGN0@Gf>_;Jmxbp+J13zX24k|z@bQPb{EF0CmfYbxJ~O%k&CRSN)_G#!raT! zB2RD$R7b>ld3eqnaC6j5`6O>B8pXLa+91{US;KR#c0HAi>FV66qJsEf-$^exLCvJ- z%W|Bbzf?`*EQ`~OSQ*Od+B(aTqp3|iTw(MPFU$~VJ?;aGI!+E-Q!o?glyj^!ZbYbP z<81oFtestXwVDMoCi>Qzy^KTP|UDdNG+sSR`)Vuyu=yjHg}M zH5*jBS^y(;6%bRP17M-JlrwwBuLqQr?LxNQ2Ap(AZMr6W(R`uRYsO5SbV3s5B{#F3 z?bTf&Y=oM+5~VulI}4c$ywg?lF2$O-J>>{pyh0HWwX(|vc5$0dm&0?WCd+4+EVI!Y zSIvL^QCh;@6C7H)WSzV1jZquFVu}7kr(#T4NoZNQwWz+Hsot|!kK&TG%XbyRqHrU> zrDUs0vWz(H*cQ)oToQ|KQbzl`y7D9&Rj64em4>mS`oNM&In6ragv)*h3eOeJDClzv zrUIRU#P6attq&SbC2NImfWuB$|KFHY&nHXNgaXGshn8el`2t4$ssAr zg-enc+Mjah;dVLBDhMKtMEzQ6_8=ffITG6I%4%_Z1t~EgEIs%Y50R-}{lXPtaRnGM z%m{fOr=JJ$_yGCStL)*PzUStktKI5vCo@J|sw0KJ8P70OktWe{=ZBoUg49f1TqwLl zj*zTG2hq?vo)*iz!b|MDT0pU`bU3<|A(po40|gRSfg_q4KxI5qwo& z&vU_ttugmMuaC9=bfLnK70l9k%ub9E@1oeeAm5%70H@0l36AOzh_dm7SnIW36a>X1 zM~QH{g@HC5TaYNAZGeh}>7I}Ph};P1A^_1^orKh*jUSKaVq_nG>tL@gP?*|emk;O8 z$Qb+&V?7I{%@YTZ<%k{ecMc$xmSy4qQV}Kg5;S0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU>rAb6VRCwC#U3-ib)fqo~cST+<4|%$* z3lyshD328_FS(XiX+bf*uz|3q1QXLHO-VGBS{5{INtBqh|Im=IHqs#0KTM!0mB6kM zS3zsF%JNuVtD6-F;Ih2s(eHQmoa@=cnR{pE&fI(F-t#42W_ISxcOKvGeCPYVId`_` z`t|F~lBJTBD_1(+yyK2LM$ut9@kHW#h${lBw}&{M%S0yucJQ@p*VyIDmt)_3_gyjg z>eZ`7SFT*SD2g>xng3Cl2I6hYmoNXKrlzJ~W=Ros)&zi|bf~6*Sxkd66ay$ZY9s+b z3I_-n0e}$z64zy)VzC(KqM{=3QR2T*`C2M_s30tY0R(Y6ETe&&i^0Ocl$4Yt1}bVG z001styvPB7quwW+t5yT$2^V7vS1w$*u%n=W01(h%{D}tRe*AsYO*bW! zF9$4YARr~OvuDq85a@kMm_^Yy97*{J3qsc5r&P9ow{G2@&v!7CXv+aC!ToZ^fTFw{ zT)K3LojZ4qgFx+*veQ&la=_71QWEJ-L9y@y zU=5@tkbR=r&2G8n7UmQ!_3YV`mroDM$Jv3OX zYuB#a{NisA%_M$ZHP`3QpXbkE=3&smCEx+1R+X)T0P?|*gEF|@ zy?b{yZQ3;c{LY;_S$lhXC(8g3OrAWMm#wd_=l#Mm0(n$!F}39_YTFn2yfjQlbl~Md z49=M|XSkvsc;EqkgaQv7IKY~kn%IO16Zrk($B**?#^B=qx8Hut?!EV3HhA!0u7o{% z_V8zV_wLQj_-(h{#_qiHP7aRN)>gKE|9%dF5hF%$5aAgp2NA$G-+aTBYXT^qkzkaB zX8^)mZ@rZ({QdXe=l%l-Rsz@1kCP`)^0G3TfIMn{1px}7^cVTOG)zQiy*Pvz(0={; zu_a5Ea3w;Sknz}KkFisyPO<6Jr?bk+%7kf#QUHQypM92tq`A47gJk~v`Rt*G9%3ym zEgS$(Jn;lC3jmBBJ(>+2+JVnmvu1Gs4I4I$4Ie(7*KP7&RPSH)1>`kY0w@{^pE6|% z2dT6K5(FqCLriHM#c^W3JB#;VP^CAWYH2{()2C0fMT-`3W!Bc#vR=J<@!8$j*x1R@ zlnTLYA3uIPpTTR_u4Qd)ZG6_@`s~@Wxne6SI`UN;Hf-R_!meGrSb2GQVoBJ(eLGhY zW-q{pnXR`~&3TymB*6g1Q?4{rj^X zJ$kTh+qQ9~^zGZ1KMUnbXGgO%B|)HC0u^8+ zbP;^*xh&E&6SUtJa67Swq?r}HfPS9L?}0K;6T3C*VWarNs}hAF=NJXV8A(?F=GZ- zFz!9_$Rljx#ECq+hjsxlAAa~D2NIq```{l888U<)Vb0-8n3e@OjANQb$9Xob#`A|BR2*`(130WY7mjI2) znN`7%3T|j&msSX}&@LQhpHTnInKOC&BnVUj;BX)1 zaUVdI%L3JNluqa7YeFgU8k)dRLj%1+RbEs{hOGbs0H^i|V1QzE5U60l^AZFS#01P$ z1jZGG5H-{i>Z@o*&5gR}42uDa0&Aism_Vw6O{4<^7KdbQAuCf`2vGxK_#h3H4VD@oL5C>x?W2TKu`zz z@y8!e{Ec(dOvE|%7Rqb#+^aIwdJX#n9nbY^76buW1UcI_ZQ8^Y>y}U!&-gLVYP~@$ zA(ql~7F9Fs1q><(bQxZ(f@-utHDGeZ~CfAEKP!$BlcwMVare0TcDnPi2 zG%R_r2y!rW1vrtZoF8SFlqfBdUWdGgmK=NpH}Sfgc;8QEF$hIks}NuwA=$fr zw*xIchvO-akm-6YB?%vsnQf;srm)TLeuPZcYstY!Fb$%c*%(G5C=>NsasWcMQnNNC zoMS++n^!H_&8wEo&Z6DCYMsR!2Gr{&Qoqik+%&7_Crot&k=nN80ED2-YC0Cdtzu0n zXKN7x^bvIP>-BZ8-%X^d0{v{7wVPK%SQ<=9N%r((+N-;aV1x#kby>3-=s@G}rYCh*B z2mmkB0slAS2SV{)2*AKMjW-h@e&^XDn4G;-CfWoDKQlAg=2f@;Lq1pp0>B^W@Fa0L zy5v&~fP??cH{DOj$IcBE&L2q7lbInh^IRb*k83KVM)U{aDZ3co<8aM1sq)wY= z-PRk$jjX~%FZ2-+1VN~Cp=p_jIjy&;LN#3}L7C@l)tf=b@RamS zuxaI@ZQm~qrhk;}W)o?;zi!G#y;&f#qB3} z!>0u~3<8;3SYUw=KHV3EHd-KrPZd#UVJ^1HmI!6xKeRkg{IFKwCxYWtW;H3T!6OJlksC4U8ih8-#EjLI)ua`~z2`+) zFP6Xrf~IujMv#i5(8icB+p4ngkFev0!VinGZs}122$+!FoQ7OWAy7=L-I7|MC^FS8 z4&`TikZmk0WXDBae&kvT2?)}GJ0U=DSSYLrOs9jD@>L#Wt7Qi(@|dV2F6#6u*HTD8 zNJT;Xf8Z;2sB<3(@&^(9iR;L58z!8+>tJQpGNr5=Qp=uD_Ev{zqD+9G%%@ySApzlS;&+s5w+lt-^o}U&mUOw2>O>uW4Qo&)#jTqrasy%bPjV0O) zeob&-6qeiw(NWDG%E>m`w~yh|t|+w80wH|bnB#r|+P2XGA!HICt~i*(O+1J;s{0Y} z-n>$H^)jv8$~xW_Hqu((=BJjhfXU*}hAQdut63-X+SqrAh+`n2_C;hTN$73iw6QXF1fQ z&(zQA#8~G{A53&Dt=mqfdW46HMmXdZ&ImA^@hLaT^dj-`9B9XJ(H5oxK{{~f1PD$G zh1G!R^cAK2U=Nk4WnWR`F;PdYsMD+5C|d=5$7rq6?`#n2UaNmo$xZlpEr80eF-sB* ztLX4XmU0WI^9|7krURi>oZ|xu)#SMK5zbx>m`gYpsv@GeM3pzRa`2-Q0gG8$wS3W4$(xZ070owx%mW!H5u|5P{$f45J(_2 z67N;6^%9EGX|E{jmUOw2^my8<+rceT&(CA0c-Ek@>H)Te4(dLtC;yuw;Q?NhA4v1UM!q$b)YVqY{$;ng|ZvAP$~rcsnEtkuLrVhy@Uj5J&+X7y9|1o{+dyFW zhY z+2P9bJc0xLmH4*=fKzsu3KIzRtRvC$UpqX734~^00sbCBJ#p6Y`t+>B-$9a05|MJUF0->4SGK<7F5TJ=lWKN6+&IhzfIXTxP_BAUYSlC~k)ya96~A ztE!1Fs3@{uSCwU7suGg-Dqm*4{6%~bnXhbax0`G>8;y^TlaJ!y=Z$>*$bCxrnvF(o z`x+M)R}d8y<%*4sb=vKA7v=~-N$_0n-ZRLqHxCaF-bMz>;maqN%k>M_92kbLQc)rz zB8pyLU&UAqOwrNN6cZCek&%%UV)*w#qa-FKlGEv|W5{3m+Nvr57=wC&(2ff5mzNh( z17JKGEL7k>2JHbJV~7v<&GHpkvnUX*@G!g&=-b;{j2C0X$H#{?xZRXAJPcs=9iF5m z$;ezRVzj2FM&MRfRz${TF_i##NF31cG^#Z`@cilNNv;SVRNO-;YUtmzPp;pO=?M$H&KXa&kgtWo0s6lPBgmI5?pE{Cs(iF?a`l;~DyD zeo-f&{o>+6-iK$tg6sHliqT3V>L zx0mkj?xbHsLxUI=K#h%!R99C=xw*MwjGUYtIy^k2!oou8>FJR^X=!QH*48EpNJ&YN zz7-V})Y{rg#l^)k2J7^-2D?WJrFf_^uu26000%G@R$X09v$M0`^;N^cP^fA+Q(jhM zR905f>guZ8WBY@$Q1eGn3BF&S-6IO?Hdx z>uV}0DWR^eF6oE&U>w5J%A>2v)`ghfQje?W0Dg6KC9&Gv+$?R{&z#uwe4u1DKR++| z0`pl5JPg3TzCNn2uNR5JkaKf$B7Z_1&}6(^FbrUZ(Z+b=uwC z6(A&3NPBN@PmI#h(ILt+GyQw^>CHgU3XH4^;Dv>Sz>|8Qt(jZ55^YOLN}|opO-W|v z)`SB2*4EZaaw;z`7Z10$w~KTUvj`0&EQiD4JrpU!n-28IYLy5594xT8W%)a>_yR`3 z4uAv%?*STd2moYs%#XI6ogI-V+As$Uv9+}&&z6>!Bm^*iV`GB~3JU1x=tz`+@o0Yw z+J^WV9UU#@iT~smWm{7`EZfx7BohS#jDGsnrH2m_UXQDI4>Bu&@Eaf)XZ|(~k9jZ_ z_ZWxq`emr|Xzc63$X|%rJll+IY;0_V?|(O6W`YCeEnxjf(UTDFL-@J*W=y-;YJV`; zte&CGf|v^iSUrL(V1rF)A^YGM9v(j6)c=JS`mZnWV1WUv&|&|TH8L{d9vmF}hxfbR z82m3^-_rhKSS4+OhpU&ZpOyf34Gj(b&6|EdCyLJu@m1w&8bCiKt?{ei;p}BIk$v;W z;K0BD2qXDg#@j>#gM45R2lxBQbC~&$0rR=di9ZMN#~@dC2t52le+>RFzyR~BA^19M RLBjw5002ovPDHLkV1n=}nO*<@ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/btnPrevious.png b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/btnPrevious.png new file mode 100755 index 0000000000000000000000000000000000000000..e0cd9c49af7fe2f6ab694843bdd3b90ce9217cb3 GIT binary patch literal 1442 zcmV;T1zq}yP)DPR}?-yIFE=Eh>D`1 zxDZ4LPAETaM%Rg1tJM?Bq#w*j8W7$G%8Lw&qL>a zCw-c0=k{xpz@y)ROWmrvb?SWQJHxHA4-E}n(SMgP*YTP@kUTy+|!o71v*0Uj9&rU)Awvb?t|yrluuz?lkGJ^!4>Q=_3#Yh7?%3ySpO=mYhx}U&DY{ ztyUfuiv?p_UHazs_V(+RmX<#~!COX0NBtf&z+PNj(ACuyg9R|V-7WxmjW|KE>|Uds z@STc?cg@YsKf-UK&-yU{K0Q69%gakX4+sdLz`#JA)xX(ON1M&Y_pk=Ql>x3!!mdfe z!^0^!IG908e&ZRJBWj|fqrXs78-l{_=_v@jjIfe|ffq^G2m?4m^pL_e6dJiF1qN%N zN7iBv{W#*>#30@cz{pcPr!(of7J!S2ium5d#02Y>wRk`5Q93?XY=7I{-o9Wrc}-P4 zijR+f<+;7RJ8Nysxo|~Jaii!%Fot>rm`FYk+ zS69dHT3lRYgFQtzUOxh`I6)W|z%w&5?&_^S2ghNqocjra(~1+%0Vc$oy}iBE*4D=J zf`S5W8<5G#N$Tn8;ro!p#6)%$WMgB4Iy*brNyEd#%-Ht!Hs$8#((&;zr$TIOEG;Z7 z@LUWu*&qh%L!_jnsm&{8?^!)!Wk?@MMb%{ePRf%0|=@*@(HY=il?Tga{EH4qxvIv(ca)R=-l7m z=dj0mge~-X6I1W@6l_i33LLtW&2z{I<5w2)1HBr?f(#o-m~a)XCyiluc>E>604H)GzA+XomH+?%07*qoM6N<$f}$awVgLXD literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/default_thumbnail.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/default_thumbnail.gif new file mode 100755 index 0000000000000000000000000000000000000000..2b1280f32756805aaa557cea32c70b05a2aa46b4 GIT binary patch literal 227 zcmZ?wbhEHbG-6O>IKlt||NsBb%*?E=uIA+AG%zrD`0!y=R1_N<+vm@pjg5^ZBqW4{ zgcKDO=ggUN^5n@6A3hv7aNygwZyp{VGiT1cdi82iQIVXS9M~i*gyK&Yu&54*1lh^J znxLT2my$UzW7WEx*ZT@|&X?rguUPlK=KX)mqpTdl9&9X0-U1vFe5XV=OIC<1TA^wB zAR@v0R&G+mkFG;kP93Sa$EGmNNLpZ-!L!AgPrAh9_7P-L(M E0LAB0sQ>@~ literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/loader.gif b/widgets/jqPrettyPhoto/prettyPhoto/css/images/light_square/loader.gif new file mode 100755 index 0000000000000000000000000000000000000000..7ac990cf04f2323f9723518bfd72ce102222cefa GIT binary patch literal 2545 zcma*pYg7~I83*u}xzA)~n25ScX&WZP5>nU=1T3OL6cUK&3gY5=2L-vPKosR#4J1Gy z2{*Y2f`*F<925Z+DqcWPab1;V*Q&?%tge1gx39sBO@dC?%nI@=>b1`dA^f;rd<;kzBEP~@QDg`5nuHDmwWi`ccB(8fJu!D@W@6GOX&Kuyf#(~9#Kot4om)5$3D8qAS)~A$Ca-6yH;bP-YR%)e z*uVwm#a35*X`LHGj32c;jsl4QZfgsvW!V?{2-SqnJnoXXF*nkrHs1S%=LGrPst~{V zbIXcXr&l++uUTGfQbH(6qL7Kn#+Xt|g{`v4+z9V)In}XN;&Aif7dZ&S7naR;h=?Fn^{lc|@~&lZlv?W9&si{Zho3Zq zBNC0W8pT9f=}<$GQHasxVL1urLe*G$-i1I28VmOw4D1-nlxMUF7p{*V9W9#? zG3-PbWllts&>7IxQA0d2*lgcSw`;G+rpt*+JWGhuM10z1YIElzOCrY2>B0NtO_MPS zMFf29+_^(8vP$B!PKEl9XU62S@@i=sRcJ^lp7MFFlGzbm>4Ok7O@u`=7$RC~4%Sy4 zYHq1ICcqWPd4_@-lucMS20@nSCdAI1T?+2+dcgowe`%@+K-I${;uYHgEWPq*J&<;{Drqc!cL#1&wL zi~k_=rv}{|hWqv;9yTrI81Aln{6ubW*40;m3xk6_R;~^o)wq1blF3R~tzpvi6+Rf{ zO@-+>SE~;p9EM~ zU%BS@TGN0@Gf>_;Jmxbp+J13zX24k|z@bQPb{EF0CmfYbxJ~O%k&CRSN)_G#!raT! zB2RD$R7b>ld3eqnaC6j5`6O>B8pXLa+91{US;KR#c0HAi>FV66qJsEf-$^exLCvJ- z%W|Bbzf?`*EQ`~OSQ*Od+B(aTqp3|iTw(MPFU$~VJ?;aGI!+E-Q!o?glyj^!ZbYbP z<81oFtestXwVDMoCi>Qzy^KTP|UDdNG+sSR`)Vuyu=yjHg}M zH5*jBS^y(;6%bRP17M-JlrwwBuLqQr?LxNQ2Ap(AZMr6W(R`uRYsO5SbV3s5B{#F3 z?bTf&Y=oM+5~VulI}4c$ywg?lF2$O-J>>{pyh0HWwX(|vc5$0dm&0?WCd+4+EVI!Y zSIvL^QCh;@6C7H)WSzV1jZquFVu}7kr(#T4NoZNQwWz+Hsot|!kK&TG%XbyRqHrU> zrDUs0vWz(H*cQ)oToQ|KQbzl`y7D9&Rj64em4>mS`oNM&In6ragv)*h3eOeJDClzv zrUIRU#P6attq&SbC2NImfWuB$|KFHY&nHXNgaXGshn8el`2t4$ssAr zg-enc+Mjah;dVLBDhMKtMEzQ6_8=ffITG6I%4%_Z1t~EgEIs%Y50R-}{lXPtaRnGM z%m{fOr=JJ$_yGCStL)*PzUStktKI5vCo@J|sw0KJ8P70OktWe{=ZBoUg49f1TqwLl zj*zTG2hq?vo)*iz!b|MDT0pU`bU3<|A(po40|gRSfg_q4KxI5qwo& z&vU_ttugmMuaC9=bfLnK70l9k%ub9E@1oeeAm5%70H@0l36AOzh_dm7SnIW36a>X1 zM~QH{g@HC5TaYNAZGeh}>7I}Ph};P1A^_1^orKh*jUSKaVq_nG>tL@gP?*|emk;O8 z$Qb+&V?7I{%@YTZ<%k{ecMc$xmSy4qQV}Kg5QnSP5?=@`DvWA}t|^%OI}h zP*!BY9Om*eGGKAoU~X{US5!@krn;xAr)PRb>5@v*UH$64dSAWw>UGt4oYmUe%EIEl zgyO5dyc@tXZ>89XfRAv1&Fe z;D+2!b8|C#C4-^>4Aup(RGlb3^U!0L9dz#8nbW9!h}W+Ov=wR$8zBNbCvux=fF84m zfx-M?=Iz_J=QD|Wly>df@pjcs;2!9QS;pW{K?5bBAS!r+5BPyj25V^@7^lkH3KsaI zf)8lW-mzmxHe<#N-v7jj6Wmo*O9NO_rcB|qM~@!mdco4bkLW(%x^?S!60kC?h9 z-MV$-?$f`2e>Qyha1LTaLj${d^(qIeu&|JWEn^K&d;0WgY{5et^my>#0jHI&34S#G0s_8Fx?~9&Q~6{{%aUM$My4Lp+r(#y zw6M_-E?2^ME(zL4j~=n*%a?PdtE;P7*REZ;wba(u#(ZDqKi~)J89#nJw}|cAx3i|E zCO(U3FE20W02CCw&x?2M+Qr@R%$YN6;J|?~S3G?9FjofFfJuU-SjVd7j5?hpXaETP zr%js{3mpDR9xzlJNpa?p1D`gPt0SA>-o7ZvQMM zv7I}2vem0s^Y-rDyT@Ge^5x4sP@g?}miO0Q4w3>`}Xb2V^K{_4Vyf9G8;2y z3eHtW2kO9q1MJ3)8$5m@4kOfHL9+G}Z##!2U=eDokTV&t$UF!ZbLGkv9>`^yaO~JI zo=!kn1au4x!4(zh1^_?-fQW&hM@1}wmBHQc3^;2T-~Ci zg60Gkaxu6tba4Vr0QQaCtw7@XJ#`#xVrA0rQ874~xWas4aWa;nz!Crul*TiexPl&T zjB5!np7$b%OjkbLQ~zNwBP&4|ooGJjaAfcO*Ht>2jWw zc9910Ee&$B31DCZ>(V8UP{^)W-AL`D>jzMr_*t!AD9Ed7WdHy`;JFnn6tv+v=p|T+ zb$z>T-MXLD>8U0G6KIC!xm_*OwE#3I3F}l7SqYj`u>uLuiPv5NzWRgh9ddQ|#?Hn&Ui9NHk1o&4QHf*^0(S8_#Rzt%6KtjHUpiJ8Xv$HZ(EGBMs0u5-X z|38V(dUXTwBiI2N1=CxE?s@8v(Oc9KG`Xt*Abi`Rw!F^3Ju9ixcX&gm`hRr@Lha6n zAYpM|vO@?$z>->c45N3zR$ah!+80wd=B%Z*w${~7WKeRGZei+ilp08#X#HB{f{+^* zFjbh`8nJHgsxz+}oZ8P6kW|Y}7%+7}OP4N<-Q!v}v$)3IyKIx!rh``HYWB}a@*Su2B2l-t zbIo|;No5Z9Q68oyuWn{j&6k{BklmyrvcH|B+O>O?>&we`ynM$^zT_p-_e0@SS=k49 znA2X9+X0EMVH)LOsg~>OewfpKm76re>wZ`&<@)yWZ92QjNH|j=H(|gi2Kj2r_=7K( zI~{?@O&Bl=+`b-0;IYdEBM`a1y-UU$WOxV?9)ctm^sRzT4T89_o|fgV>aC@hUqqo*TV=^Q7?xtsXheBwz`=5PUkdu-7AzIhajnt z>x+fixrc7>xomdJvW(5sQ|^&{Db5q5_! zZsi7XAd+nt58`cATLnS7B>fld3K1Tx%@x z`?$SmFCgmxvimAjTJ489?N{0DMR}d+@DRiY_sxhykW_rH zN>_rC>uXvOmJqNIupmn;hV0m}V<3Gjrdm#Qf@nTZbwAZTt5&W084Yg#zEaZp%{MJ9 zjFiG(+N@nG5iVa_;$-#{HrbW=F z0pn*Z*HZl*)qjb&)hMpfwpP#>m1OY^G}AZTjC1?N+s&IdCxvggTM=&Wgn)%bDY^3W zh4Q1W^id>MG5I>wAa)mODL6Nw!R@|PviPQjg^4Ym3if`V3W{|SwNSKG2pXf3EWUxR zlEMO@)F(>&L#jP2w3n&wi$=$fW-vw zrUdu~4w#yGH$x$cE6q0ag$BF}ZIan$0a~+1zQG24B@nj=upSA;Dn%T^umAsE(BvEN zR%yQ^eoqz99uZ%F_8Sq?@-xZ8;uSxByfyRwN120#H7G0C3Yz(y!UA;be1ie+2F2CZ z5kF~Nm2}-5ve^HA&$S_wEUZqs&l8~K+0fS+@UF8-HSz>lc^>%&4p^;N+GXh~6k?jR zqSk=7m9H!-gbc5Ig9S`v+9xc^>2k0WGAnnvwWH(9G&z_N-?Rw&Vgqjy*#<1i?R^8P|~ zJJk+0_ZbJ+FGOy%F-b}wX~jQXYio0#kueNF{Ye1#>o97$5BhM;T~vRdIyQvL{VEAJ z{WsNbsP+p1b6>F8cTL2%wjp5d3cj0)?{d8g0dp?cP56T9IPuaPCaw+XRdhaUg{002ovPDHLkV1kA;p5g!i literal 0 HcmV?d00001 diff --git a/widgets/jqPrettyPhoto/prettyPhoto/css/prettyPhoto.css b/widgets/jqPrettyPhoto/prettyPhoto/css/prettyPhoto.css new file mode 100644 index 0000000..baddb1f --- /dev/null +++ b/widgets/jqPrettyPhoto/prettyPhoto/css/prettyPhoto.css @@ -0,0 +1,453 @@ +/* ------------------------------------------------------------------------ + This you can edit. +------------------------------------------------------------------------- */ + + div.light_rounded .pp_top .pp_left { background: url(images/light_rounded/sprite.png) -88px -53px no-repeat; } /* Top left corner */ + div.light_rounded .pp_top .pp_middle { background: #fff; } /* Top pattern/color */ + div.light_rounded .pp_top .pp_right { background: url(images/light_rounded/sprite.png) -110px -53px no-repeat; } /* Top right corner */ + + div.light_rounded .pp_content .ppt { color: #000; } + div.light_rounded .pp_content_container .pp_left, + div.light_rounded .pp_content_container .pp_right { background: #fff; } + div.light_rounded .pp_content { background-color: #fff; } /* Content background */ + div.light_rounded .pp_next:hover { background: url(images/light_rounded/btnNext.png) center right no-repeat; cursor: pointer; } /* Next button */ + div.light_rounded .pp_previous:hover { background: url(images/light_rounded/btnPrevious.png) center left no-repeat; cursor: pointer; } /* Previous button */ + div.light_rounded .pp_expand { background: url(images/light_rounded/sprite.png) -31px -26px no-repeat; cursor: pointer; } /* Expand button */ + div.light_rounded .pp_expand:hover { background: url(images/light_rounded/sprite.png) -31px -47px no-repeat; cursor: pointer; } /* Expand button hover */ + div.light_rounded .pp_contract { background: url(images/light_rounded/sprite.png) 0 -26px no-repeat; cursor: pointer; } /* Contract button */ + div.light_rounded .pp_contract:hover { background: url(images/light_rounded/sprite.png) 0 -47px no-repeat; cursor: pointer; } /* Contract button hover */ + div.light_rounded .pp_close { width: 75px; height: 22px; background: url(images/light_rounded/sprite.png) -1px -1px no-repeat; cursor: pointer; } /* Close button */ + div.light_rounded #pp_full_res .pp_inline { color: #000; } + div.light_rounded .pp_gallery a.pp_arrow_previous, + div.light_rounded .pp_gallery a.pp_arrow_next { margin-top: 12px !important; } + div.light_rounded .pp_nav .pp_play { background: url(images/light_rounded/sprite.png) -1px -100px no-repeat; height: 15px; width: 14px; } + div.light_rounded .pp_nav .pp_pause { background: url(images/light_rounded/sprite.png) -24px -100px no-repeat; height: 15px; width: 14px; } + + div.light_rounded .pp_arrow_previous { background: url(images/light_rounded/sprite.png) 0 -71px no-repeat; } /* The previous arrow in the bottom nav */ + div.light_rounded .pp_arrow_previous.disabled { background-position: 0 -87px; cursor: default; } + div.light_rounded .pp_arrow_next { background: url(images/light_rounded/sprite.png) -22px -71px no-repeat; } /* The next arrow in the bottom nav */ + div.light_rounded .pp_arrow_next.disabled { background-position: -22px -87px; cursor: default; } + + div.light_rounded .pp_bottom .pp_left { background: url(images/light_rounded/sprite.png) -88px -80px no-repeat; } /* Bottom left corner */ + div.light_rounded .pp_bottom .pp_middle { background: #fff; } /* Bottom pattern/color */ + div.light_rounded .pp_bottom .pp_right { background: url(images/light_rounded/sprite.png) -110px -80px no-repeat; } /* Bottom right corner */ + + div.light_rounded .pp_loaderIcon { background: url(images/light_rounded/loader.gif) center center no-repeat; } /* Loader icon */ + + /* ---------------------------------- + Dark Rounded Theme + ----------------------------------- */ + + div.dark_rounded .pp_top .pp_left { background: url(images/dark_rounded/sprite.png) -88px -53px no-repeat; } /* Top left corner */ + div.dark_rounded .pp_top .pp_middle { background: url(images/dark_rounded/contentPattern.png) top left repeat; } /* Top pattern/color */ + div.dark_rounded .pp_top .pp_right { background: url(images/dark_rounded/sprite.png) -110px -53px no-repeat; } /* Top right corner */ + + div.dark_rounded .pp_content_container .pp_left { background: url(images/dark_rounded/contentPattern.png) top left repeat-y; } /* Left Content background */ + div.dark_rounded .pp_content_container .pp_right { background: url(images/dark_rounded/contentPattern.png) top right repeat-y; } /* Right Content background */ + div.dark_rounded .pp_content { background: url(images/dark_rounded/contentPattern.png) top left repeat; } /* Content background */ + div.dark_rounded .pp_next:hover { background: url(images/dark_rounded/btnNext.png) center right no-repeat; cursor: pointer; } /* Next button */ + div.dark_rounded .pp_previous:hover { background: url(images/dark_rounded/btnPrevious.png) center left no-repeat; cursor: pointer; } /* Previous button */ + div.dark_rounded .pp_expand { background: url(images/dark_rounded/sprite.png) -31px -26px no-repeat; cursor: pointer; } /* Expand button */ + div.dark_rounded .pp_expand:hover { background: url(images/dark_rounded/sprite.png) -31px -47px no-repeat; cursor: pointer; } /* Expand button hover */ + div.dark_rounded .pp_contract { background: url(images/dark_rounded/sprite.png) 0 -26px no-repeat; cursor: pointer; } /* Contract button */ + div.dark_rounded .pp_contract:hover { background: url(images/dark_rounded/sprite.png) 0 -47px no-repeat; cursor: pointer; } /* Contract button hover */ + div.dark_rounded .pp_close { width: 75px; height: 22px; background: url(images/dark_rounded/sprite.png) -1px -1px no-repeat; cursor: pointer; } /* Close button */ + div.dark_rounded .currentTextHolder { color: #c4c4c4; } + div.dark_rounded .pp_description { color: #fff; } + div.dark_rounded #pp_full_res .pp_inline { color: #fff; } + div.dark_rounded .pp_gallery a.pp_arrow_previous, + div.dark_rounded .pp_gallery a.pp_arrow_next { margin-top: 12px !important; } + div.dark_rounded .pp_nav .pp_play { background: url(images/dark_rounded/sprite.png) -1px -100px no-repeat; height: 15px; width: 14px; } + div.dark_rounded .pp_nav .pp_pause { background: url(images/dark_rounded/sprite.png) -24px -100px no-repeat; height: 15px; width: 14px; } + + div.dark_rounded .pp_arrow_previous { background: url(images/dark_rounded/sprite.png) 0 -71px no-repeat; } /* The previous arrow in the bottom nav */ + div.dark_rounded .pp_arrow_previous.disabled { background-position: 0 -87px; cursor: default; } + div.dark_rounded .pp_arrow_next { background: url(images/dark_rounded/sprite.png) -22px -71px no-repeat; } /* The next arrow in the bottom nav */ + div.dark_rounded .pp_arrow_next.disabled { background-position: -22px -87px; cursor: default; } + + div.dark_rounded .pp_bottom .pp_left { background: url(images/dark_rounded/sprite.png) -88px -80px no-repeat; } /* Bottom left corner */ + div.dark_rounded .pp_bottom .pp_middle { background: url(images/dark_rounded/contentPattern.png) top left repeat; } /* Bottom pattern/color */ + div.dark_rounded .pp_bottom .pp_right { background: url(images/dark_rounded/sprite.png) -110px -80px no-repeat; } /* Bottom right corner */ + + div.dark_rounded .pp_loaderIcon { background: url(images/dark_rounded/loader.gif) center center no-repeat; } /* Loader icon */ + + + /* ---------------------------------- + Dark Square Theme + ----------------------------------- */ + + div.dark_square .pp_left , + div.dark_square .pp_middle, + div.dark_square .pp_right, + div.dark_square .pp_content { background: url(images/dark_square/contentPattern.png) top left repeat; } + div.dark_square .currentTextHolder { color: #c4c4c4; } + div.dark_square .pp_description { color: #fff; } + div.dark_square .pp_loaderIcon { background: url(images/dark_rounded/loader.gif) center center no-repeat; } /* Loader icon */ + + div.dark_square .pp_content_container .pp_left { background: url(images/dark_rounded/contentPattern.png) top left repeat-y; } /* Left Content background */ + div.dark_square .pp_content_container .pp_right { background: url(images/dark_rounded/contentPattern.png) top right repeat-y; } /* Right Content background */ + div.dark_square .pp_expand { background: url(images/dark_square/sprite.png) -31px -26px no-repeat; cursor: pointer; } /* Expand button */ + div.dark_square .pp_expand:hover { background: url(images/dark_square/sprite.png) -31px -47px no-repeat; cursor: pointer; } /* Expand button hover */ + div.dark_square .pp_contract { background: url(images/dark_square/sprite.png) 0 -26px no-repeat; cursor: pointer; } /* Contract button */ + div.dark_square .pp_contract:hover { background: url(images/dark_square/sprite.png) 0 -47px no-repeat; cursor: pointer; } /* Contract button hover */ + div.dark_square .pp_close { width: 75px; height: 22px; background: url(images/dark_square/sprite.png) -1px -1px no-repeat; cursor: pointer; } /* Close button */ + div.dark_square #pp_full_res .pp_inline { color: #fff; } + div.dark_square .pp_gallery a.pp_arrow_previous, + div.dark_square .pp_gallery a.pp_arrow_next { margin-top: 12px !important; } + div.dark_square .pp_nav .pp_play { background: url(images/dark_square/sprite.png) -1px -100px no-repeat; height: 15px; width: 14px; } + div.dark_square .pp_nav .pp_pause { background: url(images/dark_square/sprite.png) -24px -100px no-repeat; height: 15px; width: 14px; } + + div.dark_square .pp_arrow_previous { background: url(images/dark_square/sprite.png) 0 -71px no-repeat; } /* The previous arrow in the bottom nav */ + div.dark_square .pp_arrow_previous.disabled { background-position: 0 -87px; cursor: default; } + div.dark_square .pp_arrow_next { background: url(images/dark_square/sprite.png) -22px -71px no-repeat; } /* The next arrow in the bottom nav */ + div.dark_square .pp_arrow_next.disabled { background-position: -22px -87px; cursor: default; } + + div.dark_square .pp_next:hover { background: url(images/dark_square/btnNext.png) center right no-repeat; cursor: pointer; } /* Next button */ + div.dark_square .pp_previous:hover { background: url(images/dark_square/btnPrevious.png) center left no-repeat; cursor: pointer; } /* Previous button */ + + + /* ---------------------------------- + Light Square Theme + ----------------------------------- */ + + div.light_square .pp_left , + div.light_square .pp_middle, + div.light_square .pp_right, + div.light_square .pp_content { background: #fff; } + + div.light_square .pp_content .ppt { color: #000; } + div.light_square .pp_expand { background: url(images/light_square/sprite.png) -31px -26px no-repeat; cursor: pointer; } /* Expand button */ + div.light_square .pp_expand:hover { background: url(images/light_square/sprite.png) -31px -47px no-repeat; cursor: pointer; } /* Expand button hover */ + div.light_square .pp_contract { background: url(images/light_square/sprite.png) 0 -26px no-repeat; cursor: pointer; } /* Contract button */ + div.light_square .pp_contract:hover { background: url(images/light_square/sprite.png) 0 -47px no-repeat; cursor: pointer; } /* Contract button hover */ + div.light_square .pp_close { width: 75px; height: 22px; background: url(images/light_square/sprite.png) -1px -1px no-repeat; cursor: pointer; } /* Close button */ + div.light_square #pp_full_res .pp_inline { color: #000; } + div.light_square .pp_gallery a.pp_arrow_previous, + div.light_square .pp_gallery a.pp_arrow_next { margin-top: 12px !important; } + div.light_square .pp_nav .pp_play { background: url(images/light_square/sprite.png) -1px -100px no-repeat; height: 15px; width: 14px; } + div.light_square .pp_nav .pp_pause { background: url(images/light_square/sprite.png) -24px -100px no-repeat; height: 15px; width: 14px; } + + div.light_square .pp_arrow_previous { background: url(images/light_square/sprite.png) 0 -71px no-repeat; } /* The previous arrow in the bottom nav */ + div.light_square .pp_arrow_previous.disabled { background-position: 0 -87px; cursor: default; } + div.light_square .pp_arrow_next { background: url(images/light_square/sprite.png) -22px -71px no-repeat; } /* The next arrow in the bottom nav */ + div.light_square .pp_arrow_next.disabled { background-position: -22px -87px; cursor: default; } + + div.light_square .pp_next:hover { background: url(images/light_square/btnNext.png) center right no-repeat; cursor: pointer; } /* Next button */ + div.light_square .pp_previous:hover { background: url(images/light_square/btnPrevious.png) center left no-repeat; cursor: pointer; } /* Previous button */ + + + /* ---------------------------------- + Facebook style Theme + ----------------------------------- */ + + div.facebook .pp_top .pp_left { background: url(images/facebook/sprite.png) -88px -53px no-repeat; } /* Top left corner */ + div.facebook .pp_top .pp_middle { background: url(images/facebook/contentPatternTop.png) top left repeat-x; } /* Top pattern/color */ + div.facebook .pp_top .pp_right { background: url(images/facebook/sprite.png) -110px -53px no-repeat; } /* Top right corner */ + + div.facebook .pp_content .ppt { color: #000; } + div.facebook .pp_content_container .pp_left { background: url(images/facebook/contentPatternLeft.png) top left repeat-y; } /* Content background */ + div.facebook .pp_content_container .pp_right { background: url(images/facebook/contentPatternRight.png) top right repeat-y; } /* Content background */ + div.facebook .pp_content { background: #fff; } /* Content background */ + div.facebook .pp_expand { background: url(images/facebook/sprite.png) -31px -26px no-repeat; cursor: pointer; } /* Expand button */ + div.facebook .pp_expand:hover { background: url(images/facebook/sprite.png) -31px -47px no-repeat; cursor: pointer; } /* Expand button hover */ + div.facebook .pp_contract { background: url(images/facebook/sprite.png) 0 -26px no-repeat; cursor: pointer; } /* Contract button */ + div.facebook .pp_contract:hover { background: url(images/facebook/sprite.png) 0 -47px no-repeat; cursor: pointer; } /* Contract button hover */ + div.facebook .pp_close { width: 22px; height: 22px; background: url(images/facebook/sprite.png) -1px -1px no-repeat; cursor: pointer; } /* Close button */ + div.facebook #pp_full_res .pp_inline { color: #000; } + div.facebook .pp_loaderIcon { background: url(images/facebook/loader.gif) center center no-repeat; } /* Loader icon */ + + div.facebook .pp_arrow_previous { background: url(images/facebook/sprite.png) 0 -71px no-repeat; height: 22px; margin-top: 0; width: 22px; } /* The previous arrow in the bottom nav */ + div.facebook .pp_arrow_previous.disabled { background-position: 0 -96px; cursor: default; } + div.facebook .pp_arrow_next { background: url(images/facebook/sprite.png) -32px -71px no-repeat; height: 22px; margin-top: 0; width: 22px; } /* The next arrow in the bottom nav */ + div.facebook .pp_arrow_next.disabled { background-position: -32px -96px; cursor: default; } + div.facebook .pp_nav { margin-top: 0; } + div.facebook .pp_nav p { font-size: 15px; padding: 0 3px 0 4px; } + div.facebook .pp_nav .pp_play { background: url(images/facebook/sprite.png) -1px -123px no-repeat; height: 22px; width: 22px; } + div.facebook .pp_nav .pp_pause { background: url(images/facebook/sprite.png) -32px -123px no-repeat; height: 22px; width: 22px; } + + div.facebook .pp_next:hover { background: url(images/facebook/btnNext.png) center right no-repeat; cursor: pointer; } /* Next button */ + div.facebook .pp_previous:hover { background: url(images/facebook/btnPrevious.png) center left no-repeat; cursor: pointer; } /* Previous button */ + + div.facebook .pp_bottom .pp_left { background: url(images/facebook/sprite.png) -88px -80px no-repeat; } /* Bottom left corner */ + div.facebook .pp_bottom .pp_middle { background: url(images/facebook/contentPatternBottom.png) top left repeat-x; } /* Bottom pattern/color */ + div.facebook .pp_bottom .pp_right { background: url(images/facebook/sprite.png) -110px -80px no-repeat; } /* Bottom right corner */ + + +/* ------------------------------------------------------------------------ + DO NOT CHANGE +------------------------------------------------------------------------- */ + + div.pp_pic_holder a:focus { outline:none; } + + div.pp_overlay { + background: #000; + display: none; + left: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 9500; + } + + div.pp_pic_holder { + display: none; + position: absolute; + width: 100px; + z-index: 10000; + } + + .pp_top { + height: 20px; + position: relative; + } + * html .pp_top { padding: 0 20px; } + + .pp_top .pp_left { + height: 20px; + left: 0; + position: absolute; + width: 20px; + } + .pp_top .pp_middle { + height: 20px; + left: 20px; + position: absolute; + right: 20px; + } + * html .pp_top .pp_middle { + left: 0; + position: static; + } + + .pp_top .pp_right { + height: 20px; + left: auto; + position: absolute; + right: 0; + top: 0; + width: 20px; + } + + .pp_content { height: 40px; } + + .pp_fade { display: none; } + + .pp_content_container { + position: relative; + text-align: left; + width: 100%; + } + + .pp_content_container .pp_left { padding-left: 20px; } + .pp_content_container .pp_right { padding-right: 20px; } + + .pp_content_container .pp_details { + float: left; + margin: 10px 0 2px 0; + } + .pp_description { + display: none; + margin: 0 0 5px 0; + } + + .pp_nav { + clear: left; + float: left; + margin: 3px 0 0 0; + } + + .pp_nav p { + float: left; + margin: 2px 4px; + } + + .pp_nav .pp_play, + .pp_nav .pp_pause { + float: left; + margin-right: 4px; + text-indent: -10000px; + } + + a.pp_arrow_previous, + a.pp_arrow_next { + display: block; + float: left; + height: 15px; + margin-top: 3px; + overflow: hidden; + text-indent: -10000px; + width: 14px; + } + + .pp_hoverContainer { + position: absolute; + top: 0; + width: 100%; + z-index: 2000; + } + + .pp_gallery { + left: 50%; + margin-top: -50px; + position: absolute; + z-index: 10000; + } + + .pp_gallery ul { + float: left; + height: 35px; + margin: 0 0 0 5px; + overflow: hidden; + position: relative; + } + + .pp_gallery ul a { + border: 1px #000 solid; + border: 1px rgba(0,0,0,0.5) solid; + display: block; + float: left; + height: 33px; + overflow: hidden; + } + + .pp_gallery ul a:hover, + .pp_gallery li.selected a { border-color: #fff; } + + .pp_gallery ul a img { border: 0; } + + .pp_gallery li { + display: block; + float: left; + margin: 0 5px 0 0; + } + + .pp_gallery li.default a { + background: url(images/facebook/default_thumbnail.gif) 0 0 no-repeat; + display: block; + height: 33px; + width: 50px; + } + + .pp_gallery li.default a img { display: none; } + + .pp_gallery .pp_arrow_previous, + .pp_gallery .pp_arrow_next { + margin-top: 7px !important; + } + + a.pp_next { + background: url(images/light_rounded/btnNext.png) 10000px 10000px no-repeat; + display: block; + float: right; + height: 100%; + text-indent: -10000px; + width: 49%; + } + + a.pp_previous { + background: url(images/light_rounded/btnNext.png) 10000px 10000px no-repeat; + display: block; + float: left; + height: 100%; + text-indent: -10000px; + width: 49%; + } + + a.pp_expand, + a.pp_contract { + cursor: pointer; + display: none; + height: 20px; + position: absolute; + right: 30px; + text-indent: -10000px; + top: 10px; + width: 20px; + z-index: 20000; + } + + a.pp_close { + display: block; + float: right; + line-height:22px; + text-indent: -10000px; + } + + .pp_bottom { + height: 20px; + position: relative; + } + * html .pp_bottom { padding: 0 20px; } + + .pp_bottom .pp_left { + height: 20px; + left: 0; + position: absolute; + width: 20px; + } + .pp_bottom .pp_middle { + height: 20px; + left: 20px; + position: absolute; + right: 20px; + } + * html .pp_bottom .pp_middle { + left: 0; + position: static; + } + + .pp_bottom .pp_right { + height: 20px; + left: auto; + position: absolute; + right: 0; + top: 0; + width: 20px; + } + + .pp_loaderIcon { + display: block; + height: 24px; + left: 50%; + margin: -12px 0 0 -12px; + position: absolute; + top: 50%; + width: 24px; + } + + #pp_full_res { + line-height: 1 !important; + } + + #pp_full_res .pp_inline { + text-align: left; + } + + #pp_full_res .pp_inline p { margin: 0 0 15px 0; } + + div.ppt { + color: #fff; + display: none; + font-size: 17px; + margin: 0 0 5px 15px; + z-index: 9999; + } + +/* ------------------------------------------------------------------------ + Miscellaneous +------------------------------------------------------------------------- */ + + .clearfix:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; + } + + .clearfix {display: inline-block;} + + /* Hides from IE-mac \*/ + * html .clearfix {height: 1%;} + .clearfix {display: block;} + /* End hide from IE-mac */ \ No newline at end of file diff --git a/widgets/jqPrettyPhoto/prettyPhoto/index.html b/widgets/jqPrettyPhoto/prettyPhoto/index.html new file mode 100755 index 0000000..2342a7e --- /dev/null +++ b/widgets/jqPrettyPhoto/prettyPhoto/index.html @@ -0,0 +1,151 @@ + + + + jQuery lightbox clone - prettyPhoto - by Stephane Caron + + + + + + + + + + +

      API calls

      +

      No title, no description

      + +

      Gallery

      + + +

      Gallery 2

      + + +

      Picture alone

      + + +

      Flash

      + + +

      Flash alone

      + + +

      Youtube video

      + + +

      Vimeo video

      + + +

      Movies (.mov)

      + + +

      Movies (.mov) alone

      + + +

      Unusual sizes

      + + +

      Iframe

      + + +

      Mixed gallery

      + + +

      Inline content

      + + + + + + + +

      Another prettyPhoto

      +
      + Google.ca +
      + + + + \ No newline at end of file diff --git a/widgets/jqPrettyPhoto/prettyPhoto/jquery.prettyPhoto.js b/widgets/jqPrettyPhoto/prettyPhoto/jquery.prettyPhoto.js new file mode 100644 index 0000000..0e18a5e --- /dev/null +++ b/widgets/jqPrettyPhoto/prettyPhoto/jquery.prettyPhoto.js @@ -0,0 +1 @@ +(function(a){function j(e,m){e=e.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]");var n=RegExp("[\\?&]"+e+"=([^&#]*)").exec(m);return n==null?"":n[1]}a.prettyPhoto={version:"3.0"};a.fn.prettyPhoto=function(e){function m(b){$pp_pic_holder.find("#pp_full_res object,#pp_full_res embed").css("visibility","hidden");$pp_pic_holder.find(".pp_fade").fadeOut(settings.animation_speed,function(){a(".pp_loaderIcon").show();b()})}function n(b){if(set_position==b-1){$pp_pic_holder.find("a.pp_next").css("visibility", "hidden");$pp_pic_holder.find("a.pp_next").addClass("disabled").unbind("click")}else{$pp_pic_holder.find("a.pp_next").css("visibility","visible");$pp_pic_holder.find("a.pp_next.disabled").removeClass("disabled").bind("click",function(){a.prettyPhoto.changePage("next");return false})}set_position==0?$pp_pic_holder.find("a.pp_previous").css("visibility","hidden").addClass("disabled").unbind("click"):$pp_pic_holder.find("a.pp_previous.disabled").css("visibility","visible").removeClass("disabled").bind("click", function(){a.prettyPhoto.changePage("previous");return false});b>1?a(".pp_nav").show():a(".pp_nav").hide()}function f(b,d){resized=false;u(b,d);imageWidth=b;imageHeight=d;if((k>g||l>h)&&doresize&&settings.allow_resize&&!p){resized=true;for(fitting=false;!fitting;){if(k>g){imageWidth=g-200;imageHeight=d/b*imageWidth}else if(l>h){imageHeight=h-200;imageWidth=b/d*imageHeight}else fitting=true;l=imageHeight;k=imageWidth}u(imageWidth,imageHeight)}return{width:Math.floor(imageWidth),height:Math.floor(imageHeight), containerHeight:Math.floor(l),containerWidth:Math.floor(k)+40,contentHeight:Math.floor(q),contentWidth:Math.floor(v),resized:resized}}function u(b,d){b=parseFloat(b);d=parseFloat(d);$pp_details=$pp_pic_holder.find(".pp_details");$pp_details.width(b);detailsHeight=parseFloat($pp_details.css("marginTop"))+parseFloat($pp_details.css("marginBottom"));$pp_details=$pp_details.clone().appendTo(a("body")).css({position:"absolute",top:-1E4});detailsHeight+=$pp_details.height();detailsHeight=detailsHeight<= 34?36:detailsHeight;if(a.browser.msie&&a.browser.version==7)detailsHeight+=8;$pp_details.remove();q=d+detailsHeight;v=b;l=q+$ppt.height()+$pp_pic_holder.find(".pp_top").height()+$pp_pic_holder.find(".pp_bottom").height();k=b}function r(b){return b.match(/youtube\.com\/watch/i)?"youtube":b.match(/vimeo\.com/i)?"vimeo":b.indexOf(".mov")!=-1?"quicktime":b.indexOf(".swf")!=-1?"flash":b.indexOf("iframe")!=-1?"iframe":b.indexOf("custom")!=-1?"custom":b.substr(0,1)=="#"?"inline":"image"}function o(){if(doresize&& typeof $pp_pic_holder!="undefined"){scroll_pos=w();titleHeight=$ppt.height();contentHeight=$pp_pic_holder.height();contentwidth=$pp_pic_holder.width();projectedTop=h/2+scroll_pos.scrollTop-contentHeight/2;$pp_pic_holder.css({top:projectedTop,left:g/2+scroll_pos.scrollLeft-contentwidth/2})}}function w(){if(self.pageYOffset)return{scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset};else if(document.documentElement&&document.documentElement.scrollTop)return{scrollTop:document.documentElement.scrollTop, scrollLeft:document.documentElement.scrollLeft};else if(document.body)return{scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft}}function x(b){theRel=a(b).attr("rel");galleryRegExp=/\[(?:.*)\]/;pp_images=(isSet=galleryRegExp.exec(theRel)?true:false)?jQuery.map(s,function(d){if(a(d).attr("rel").indexOf(theRel)!=-1)return a(d).attr("href")}):a.makeArray(a(b).attr("href"));pp_titles=isSet?jQuery.map(s,function(d){if(a(d).attr("rel").indexOf(theRel)!=-1)return a(d).find("img").attr("alt")? a(d).find("img").attr("alt"):""}):a.makeArray(a(b).find("img").attr("alt"));pp_descriptions=isSet?jQuery.map(s,function(d){if(a(d).attr("rel").indexOf(theRel)!=-1)return a(d).attr("title")?a(d).attr("title"):""}):a.makeArray(a(b).attr("title"));a("body").append(settings.markup);$pp_pic_holder=a(".pp_pic_holder");$ppt=a(".ppt");$pp_overlay=a("div.pp_overlay");if(isSet&&settings.overlay_gallery){currentGalleryPage=0;toInject="";for(b=0;b"}toInject=settings.gallery_markup.replace(/{gallery}/g,toInject);$pp_pic_holder.find("#pp_full_res").after(toInject);$pp_pic_holder.find(".pp_gallery .pp_arrow_next").click(function(){a.prettyPhoto.changeGalleryPage("next");a.prettyPhoto.stopSlideshow();return false});$pp_pic_holder.find(".pp_gallery .pp_arrow_previous").click(function(){a.prettyPhoto.changeGalleryPage("previous"); a.prettyPhoto.stopSlideshow();return false});$pp_pic_holder.find(".pp_content").hover(function(){$pp_pic_holder.find(".pp_gallery:not(.disabled)").fadeIn()},function(){$pp_pic_holder.find(".pp_gallery:not(.disabled)").fadeOut()});itemWidth=57;$pp_pic_holder.find(".pp_gallery ul li").each(function(d){a(this).css({position:"absolute",left:d*itemWidth});a(this).find("a").unbind("click").click(function(){a.prettyPhoto.changePage(d);a.prettyPhoto.stopSlideshow();return false})})}if(settings.slideshow){$pp_pic_holder.find(".pp_nav").prepend('Play'); $pp_pic_holder.find(".pp_nav .pp_play").click(function(){a.prettyPhoto.startSlideshow();return false})}$pp_pic_holder.attr("class","pp_pic_holder "+settings.theme);$pp_overlay.css({opacity:0,height:a(document).height(),width:a(document).width()}).bind("click",function(){settings.modal||a.prettyPhoto.close()});a("a.pp_close").bind("click",function(){a.prettyPhoto.close();return false});a("a.pp_expand").bind("click",function(){if(a(this).hasClass("pp_expand")){a(this).removeClass("pp_expand").addClass("pp_contract"); doresize=false}else{a(this).removeClass("pp_contract").addClass("pp_expand");doresize=true}m(function(){a.prettyPhoto.open()});return false});$pp_pic_holder.find(".pp_previous, .pp_nav .pp_arrow_previous").bind("click",function(){a.prettyPhoto.changePage("previous");a.prettyPhoto.stopSlideshow();return false});$pp_pic_holder.find(".pp_next, .pp_nav .pp_arrow_next").bind("click",function(){a.prettyPhoto.changePage("next");a.prettyPhoto.stopSlideshow();return false});o()}e=jQuery.extend({animation_speed:"fast", slideshow:false,autoplay_slideshow:false,opacity:0.8,show_title:true,allow_resize:true,default_width:500,default_height:344,counter_separator_label:"/",theme:"facebook",hideflash:false,wmode:"opaque",autoplay:true,modal:false,overlay_gallery:true,keyboard_shortcuts:false,changepicturecallback:function(){},callback:function(){},markup:'
      ', gallery_markup:'',image_markup:'',flash_markup:'', quicktime_markup:'',iframe_markup:'', inline_markup:'
      {content}
      ',custom_markup:""},e);var s=this,p=false,c,t,q,v,l,k,h=a(window).height(),g=a(window).width(),i;doresize=true;scroll_pos=w();a(window).unbind("resize").resize(function(){o();h=a(window).height();g=a(window).width();typeof $pp_overlay!="undefined"&&$pp_overlay.height(a(document).height())});e.keyboard_shortcuts&&a(document).unbind("keydown").keydown(function(b){if(typeof $pp_pic_holder!="undefined")if($pp_pic_holder.is(":visible")){switch(b.keyCode){case 37:a.prettyPhoto.changePage("previous"); break;case 39:a.prettyPhoto.changePage("next");break;case 27:settings.modal||a.prettyPhoto.close()}return false}});a.prettyPhoto.initialize=function(){settings=e;if(a.browser.msie&&parseInt(a.browser.version)==6)settings.theme="light_square";x(this);settings.allow_resize&&a(window).scroll(function(){o()});o();set_position=jQuery.inArray(a(this).attr("href"),pp_images);a.prettyPhoto.open();return false};a.prettyPhoto.open=function(b,d,y){if(typeof settings=="undefined"){settings=e;if(a.browser.msie&& a.browser.version==6)settings.theme="light_square";x(this);pp_images=a.makeArray(b);pp_titles=d?a.makeArray(d):a.makeArray("");pp_descriptions=y?a.makeArray(y):a.makeArray("");isSet=pp_images.length>1?true:false;set_position=0}a.browser.msie&&a.browser.version==6&&a("select").css("visibility","hidden");settings.hideflash&&a("object,embed").css("visibility","hidden");n(a(pp_images).size());a(".pp_loaderIcon").show();$ppt.is(":hidden")&&$ppt.css("opacity",0).show();$pp_overlay.show().fadeTo(settings.animation_speed, settings.opacity);$pp_pic_holder.find(".currentTextHolder").text(set_position+1+settings.counter_separator_label+a(pp_images).size());$pp_pic_holder.find(".pp_description").show().html(unescape(pp_descriptions[set_position]));settings.show_title&&pp_titles[set_position]!=""?$ppt.html(unescape(pp_titles[set_position])):$ppt.html(" ");movie_width=parseFloat(j("width",pp_images[set_position]))?j("width",pp_images[set_position]):settings.default_width.toString();movie_height=parseFloat(j("height", pp_images[set_position]))?j("height",pp_images[set_position]):settings.default_height.toString();if(movie_width.indexOf("%")!=-1||movie_height.indexOf("%")!=-1){movie_height=parseFloat(a(window).height()*parseFloat(movie_height)/100-150);movie_width=parseFloat(a(window).width()*parseFloat(movie_width)/100-150);p=true}else p=false;$pp_pic_holder.fadeIn(function(){imgPreloader="";switch(r(pp_images[set_position])){case "image":imgPreloader=new Image;nextImage=new Image;if(isSet&&set_position>a(pp_images).size())nextImage.src= pp_images[set_position+1];prevImage=new Image;if(isSet&&pp_images[set_position-1])prevImage.src=pp_images[set_position-1];$pp_pic_holder.find("#pp_full_res")[0].innerHTML=settings.image_markup;$pp_pic_holder.find("#fullResImage").attr("src",pp_images[set_position]);imgPreloader.onload=function(){c=f(imgPreloader.width,imgPreloader.height);_showContent()};imgPreloader.onerror=function(){alert("Image cannot be loaded. Make sure the path is correct and image exist.");a.prettyPhoto.close()};imgPreloader.src= pp_images[set_position];break;case "youtube":c=f(movie_width,movie_height);movie="http://www.youtube.com/v/"+j("v",pp_images[set_position]);if(settings.autoplay)movie+="&autoplay=1";toInject=settings.flash_markup.replace(/{width}/g,c.width).replace(/{height}/g,c.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);break;case "vimeo":c=f(movie_width,movie_height);movie_id=pp_images[set_position];movie="http://player.vimeo.com/video/"+movie_id.match(/http:\/\/(www\.)?vimeo.com\/(\d+)/)[2]+ "?title=0&byline=0&portrait=0";if(settings.autoplay)movie+="&autoplay=1;";vimeo_width=c.width+"/embed/?moog_width="+c.width;toInject=settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,c.height).replace(/{path}/g,movie);break;case "quicktime":c=f(movie_width,movie_height);c.height+=15;c.contentHeight+=15;c.containerHeight+=15;toInject=settings.quicktime_markup.replace(/{width}/g,c.width).replace(/{height}/g,c.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g, pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);break;case "flash":c=f(movie_width,movie_height);flash_vars=pp_images[set_position];flash_vars=flash_vars.substring(pp_images[set_position].indexOf("flashvars")+10,pp_images[set_position].length);filename=pp_images[set_position];filename=filename.substring(0,filename.indexOf("?"));toInject=settings.flash_markup.replace(/{width}/g,c.width).replace(/{height}/g,c.height).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+"?"+ flash_vars);break;case "iframe":c=f(movie_width,movie_height);frame_url=pp_images[set_position];frame_url=frame_url.substr(0,frame_url.indexOf("iframe")-1);toInject=settings.iframe_markup.replace(/{width}/g,c.width).replace(/{height}/g,c.height).replace(/{path}/g,frame_url);break;case "custom":c=f(movie_width,movie_height);toInject=settings.custom_markup;break;case "inline":myClone=a(pp_images[set_position]).clone().css({width:settings.default_width}).wrapInner('
      ').appendTo(a("body")); c=f(a(myClone).width(),a(myClone).height());a(myClone).remove();toInject=settings.inline_markup.replace(/{content}/g,a(pp_images[set_position]).html())}if(!imgPreloader){$pp_pic_holder.find("#pp_full_res")[0].innerHTML=toInject;_showContent()}});return false};a.prettyPhoto.changePage=function(b){currentGalleryPage=0;if(b=="previous"){set_position--;if(set_position<0){set_position=0;return}}else if(b=="next"){set_position++;if(set_position>a(pp_images).size()-1)set_position=0}else set_position=b;doresize|| (doresize=true);a(".pp_contract").removeClass("pp_contract").addClass("pp_expand");m(function(){a.prettyPhoto.open()})};a.prettyPhoto.changeGalleryPage=function(b){if(b=="next"){currentGalleryPage++;if(currentGalleryPage>totalPage)currentGalleryPage=0}else if(b=="previous"){currentGalleryPage--;if(currentGalleryPage<0)currentGalleryPage=totalPage}else currentGalleryPage=b;itemsToSlide=currentGalleryPage==totalPage?pp_images.length-totalPage*itemsPerPage:itemsPerPage;$pp_pic_holder.find(".pp_gallery li").each(function(d){a(this).animate({left:d* itemWidth-itemsToSlide*itemWidth*currentGalleryPage})})};a.prettyPhoto.startSlideshow=function(){if(typeof i=="undefined"){$pp_pic_holder.find(".pp_play").unbind("click").removeClass("pp_play").addClass("pp_pause").click(function(){a.prettyPhoto.stopSlideshow();return false});i=setInterval(a.prettyPhoto.startSlideshow,settings.slideshow)}else a.prettyPhoto.changePage("next")};a.prettyPhoto.stopSlideshow=function(){$pp_pic_holder.find(".pp_pause").unbind("click").removeClass("pp_pause").addClass("pp_play").click(function(){a.prettyPhoto.startSlideshow(); return false});clearInterval(i);i=undefined};a.prettyPhoto.close=function(){clearInterval(i);$pp_pic_holder.stop().find("object,embed").css("visibility","hidden");a("div.pp_pic_holder,div.ppt,.pp_fade").fadeOut(settings.animation_speed,function(){a(this).remove()});$pp_overlay.fadeOut(settings.animation_speed,function(){a.browser.msie&&a.browser.version==6&&a("select").css("visibility","visible");settings.hideflash&&a("object,embed").css("visibility","visible");a(this).remove();a(window).unbind("scroll"); settings.callback();doresize=true;t=false;delete settings})};_showContent=function(){a(".pp_loaderIcon").hide();$ppt.fadeTo(settings.animation_speed,1);projectedTop=scroll_pos.scrollTop+(h/2-c.containerHeight/2);if(projectedTop<0)projectedTop=0;$pp_pic_holder.find(".pp_content").animate({height:c.contentHeight},settings.animation_speed);$pp_pic_holder.animate({top:projectedTop,left:g/2-c.containerWidth/2,width:c.containerWidth},settings.animation_speed,function(){$pp_pic_holder.find(".pp_hoverContainer,#fullResImage").height(c.height).width(c.width); $pp_pic_holder.find(".pp_fade").fadeIn(settings.animation_speed);isSet&&r(pp_images[set_position])=="image"?$pp_pic_holder.find(".pp_hoverContainer").show():$pp_pic_holder.find(".pp_hoverContainer").hide();c.resized&&a("a.pp_expand,a.pp_contract").fadeIn(settings.animation_speed);settings.autoplay_slideshow&&!i&&!t&&a.prettyPhoto.startSlideshow();settings.changepicturecallback();t=true});if(isSet&&settings.overlay_gallery&&r(pp_images[set_position])=="image"){itemWidth=57;navWidth=settings.theme== "facebook"?58:38;itemsPerPage=Math.floor((c.containerWidth-100-navWidth)/itemWidth);itemsPerPage=itemsPerPage