Просмотр исходного кода

Studio -> Store
1. Browse (public) products by different params.
2. Browse (private) Purchased products and updates for them.
3. OAuth integration for communication with BoonEx Unity.
4. Shopping cart.
5. Update package. Updated structure and installation.
6. Related fixes and changes in base code.

AntonLV 13 лет назад
Родитель
Сommit
da79500cfb
36 измененных файлов с 3816 добавлено и 241 удалено
  1. 22 18
      inc/classes/BxDolFtp.php
  2. 5 4
      inc/classes/BxDolMenu.php
  3. 1 1
      inc/classes/BxDolModuleQuery.php
  4. 5 0
      inc/classes/BxDolSession.php
  5. 6 1
      inc/profiles.inc.php
  6. 3 1
      install/sql/v70.sql
  7. 66 2
      modules/boonex/english/data/langs/system.xml
  8. 1 0
      modules/boonex/russian/data/langs/system.xml
  9. 1156 0
      plugins/jquery/jquery.fancybox.js
  10. 521 0
      plugins/jquery/jquery.migrate.js
  11. 134 0
      studio/classes/BxDolStudioCart.php
  12. 26 5
      studio/classes/BxDolStudioInstaller.php
  13. 154 4
      studio/classes/BxDolStudioInstallerUtils.php
  14. 45 0
      studio/classes/BxDolStudioJson.php
  15. 28 6
      studio/classes/BxDolStudioLanguagesUtils.php
  16. 192 0
      studio/classes/BxDolStudioOAuth.php
  17. 293 83
      studio/classes/BxDolStudioStore.php
  18. 36 59
      studio/classes/BxDolStudioUpdater.php
  19. 8 0
      studio/inc/utils.inc.php
  20. 296 10
      studio/js/store.js
  21. 1 0
      studio/template/block_caption.html
  22. 24 1
      studio/template/css/default.css
  23. 9 1
      studio/template/css/general.css
  24. 104 4
      studio/template/css/store.css
  25. BIN
      studio/template/images/icons/mi-str-downloaded.png
  26. 1 0
      studio/template/menu_side_counter.html
  27. 0 17
      studio/template/product.html
  28. 4 1
      studio/template/scripts/BxBaseStudioPage.php
  29. 478 23
      studio/template/scripts/BxBaseStudioStore.php
  30. 18 0
      studio/template/str_files.html
  31. 22 0
      studio/template/str_product_v1.html
  32. 29 0
      studio/template/str_product_v2.html
  33. 89 0
      studio/template/str_product_view.html
  34. 5 0
      studio/template/str_products.html
  35. 16 0
      studio/template/str_update_v1.html
  36. 18 0
      studio/template/str_update_v2.html

+ 22 - 18
inc/classes/BxDolFtp.php

@@ -27,43 +27,46 @@ class BxDolFtp extends BxDolFile {
         return ftp_login($this->_rStream, $this->_sLogin, $this->_sPassword);
     }
     function isDolphin() {
-        return @ftp_size($this->_rStream, $this->_sPath . 'inc/header.inc.php') > 0;
+        return @ftp_size($this->_rStream, $this->_sPathTo . 'inc/header.inc.php') > 0;
     }
     function copy($sFilePathFrom, $sFilePathTo) {
-        $sFilePathTo = $this->_sPath . $sFilePathTo;
+        $sFilePathTo = $this->_sPathTo . $sFilePathTo;
         return $this->_copyFile($sFilePathFrom, $sFilePathTo);
     }
     function delete($sPath) {
-        $sPath = $this->_sPath . $sPath;
+        $sPath = $this->_sPathTo . $sPath;
         return $this->_deleteDirectory($sPath);
     }
 
     protected function _copyFile($sFilePathFrom, $sFilePathTo) {
         if(substr($sFilePathFrom, -1) == '*')
             $sFilePathFrom = substr($sFilePathFrom, 0, -1);
+
         $bResult = false;
         if(is_file($sFilePathFrom)) {
             if($this->_isFile($sFilePathTo)) {
                 $aFileParts = $this->_parseFile($sFilePathTo);
-                if (isset($aFileParts[0])) {
-                    $bRet = $this->_ftpMkDirR($aFileParts[0]);
-                }
+                if(isset($aFileParts[0]))
+					$this->_ftpMkDirR($aFileParts[0]);
+
                 $bResult = @ftp_put($this->_rStream, $sFilePathTo, $sFilePathFrom, FTP_BINARY);
-            } else if($this->_isDirectory($sFilePathTo)) {
-                $bRet = $this->_ftpMkDirR($sFilePathTo);
+            } 
+            else if($this->_isDirectory($sFilePathTo)) {
+                $this->_ftpMkDirR($sFilePathTo);
                 $aFileParts = $this->_parseFile($sFilePathFrom);
-                if (isset($aFileParts[1])) {
+                if(isset($aFileParts[1]))
                     $bResult = @ftp_put($this->_rStream, $this->_validatePath($sFilePathTo) . $aFileParts[1], $sFilePathFrom, FTP_BINARY);
-                }
             }
-        } else if(is_dir($sFilePathFrom) && $this->_isDirectory($sFilePathTo)) {
-            $bRet = $this->_ftpMkDirR($sFilePathTo);
+        } 
+        else if(is_dir($sFilePathFrom) && $this->_isDirectory($sFilePathTo)) {
+            $this->_ftpMkDirR($sFilePathTo);
+
             $aInnerFiles = $this->_readDirectory($sFilePathFrom);
             foreach($aInnerFiles as $sFile)
                 $bResult = $this->_copyFile($this->_validatePath($sFilePathFrom) . $sFile, $this->_validatePath($sFilePathTo) . $sFile);
-        } else {
-            $bResult = false;
         }
+        else
+            $bResult = false;
 
         return $bResult;
     }
@@ -86,7 +89,7 @@ class BxDolFtp extends BxDolFile {
             if(substr($sPath, -1) != '/')
                 $sPath .= '/';
 
-            if (($aFiles = @ftp_nlist($this->_rStream, $sPath)) !== false)
+            if(($aFiles = @ftp_nlist($this->_rStream, $sPath)) !== false)
                 foreach($aFiles as $sFile)
                     if($sFile != '.' && $sFile != '..')
                         $this->_deleteDirectory(false === strpos($sFile, '/') ? $sPath . $sFile : $sFile);
@@ -94,9 +97,10 @@ class BxDolFtp extends BxDolFile {
                 if(!@ftp_rmdir($this->_rStream, $sPath))
                     return false;
 
-        } else if(!@ftp_delete($this->_rStream, $sPath)) {
-            return false;
-        }
+        } 
+        else if(!@ftp_delete($this->_rStream, $sPath))
+			return false;
+
         return true;
     }
     protected function _validatePath($sPath) {

+ 5 - 4
inc/classes/BxDolMenu.php

@@ -187,10 +187,11 @@ class BxDolMenu extends BxDol {
         if (empty($this->_aMarkers))
             return $a;
         $aReplacebleFields = array ('title', 'link', 'onclick');
-        foreach ($aReplacebleFields as $sField) {
-            foreach ($this->_aMarkers as $sKey => $sValue)
-                $a[$sField] = str_replace('{' . $sKey . '}', $sValue, $a[$sField]);
-        }
+        foreach ($aReplacebleFields as $sField)
+        	if(isset($a[$sField]))
+            	foreach ($this->_aMarkers as $sKey => $sValue)
+                	$a[$sField] = str_replace('{' . $sKey . '}', $sValue, $a[$sField]);
+
         return $a;
     }
 

+ 1 - 1
inc/classes/BxDolModuleQuery.php

@@ -66,7 +66,7 @@ class BxDolModuleQuery extends BxDolDb {
 	    return (int)$this->getOne($sSql) > 0;
 	}
     function getModules() {
-        $sSql = "SELECT `id`, `type` `title`, `vendor`, `version`, `product_url`, `update_url`, `path`, `uri`, `class_prefix`, `db_prefix`, `lang_category`, `date`, `enabled` FROM `sys_modules` ORDER BY `title`";
+        $sSql = "SELECT `id`, `type`, `name`, `title`, `vendor`, `version`, `product_url`, `update_url`, `path`, `uri`, `class_prefix`, `db_prefix`, `lang_category`, `date`, `enabled` FROM `sys_modules` ORDER BY `title`";
         return $this->fromMemory('sys_modules', 'getAll', $sSql);
     }
     function getModulesBy($aParams = array()) {

+ 5 - 0
inc/classes/BxDolSession.php

@@ -100,6 +100,11 @@ class BxDolSession extends BxDol {
         return $this->sId;
     }
 
+    function setUserId($iUserId) {
+    	$this->iUserId = $iUserId;
+    	$this->save();
+    }
+
     function setValue($sKey, $mixedValue) {
         if(empty($this->sId))
             $this->start();

+ 6 - 1
inc/profiles.inc.php

@@ -181,7 +181,6 @@ function isLoggedBanned($iCurUserID = 0) {
 }
 
 function bx_login($iId, $bRememberMe = false) {
-
     bx_import('BxDolAccountQuery');
     $oAccountQuery = BxDolAccountQuery::getInstance();
 
@@ -198,6 +197,9 @@ function bx_login($iId, $bRememberMe = false) {
     setcookie("memberPassword", $sPassword, $iCookieTime, $sPath, $sHost, false, true /* http only */);
     $_COOKIE['memberPassword'] = $sPassword;
 
+    bx_import('BxDolSession');
+	BxDolSession::getInstance()->setUserId($iId);
+
     $oAccountQuery->updateLoggedIn($iId);
 
     bx_alert('account', 'login',  $iId);
@@ -217,6 +219,9 @@ function bx_logout($bNotify = true) {
 
     unset($_COOKIE['memberID']);
     unset($_COOKIE['memberPassword']);
+
+    bx_import('BxDolSession');
+	BxDolSession::getInstance()->destroy();
 }
 
 function check_logged() {

+ 3 - 1
install/sql/v70.sql

@@ -315,7 +315,9 @@ INSERT INTO `sys_options`(`category_id`, `name`, `caption`, `value`, `type`, `ex
 (@iCategoryId, 'sys_pb_cache_engine', 'Page blocks cache engine (other than File option may require custom server setup)', 'File', 'select', 'File,EAccelerator,Memcache,APC,XCache', '', '', 24),
 (@iCategoryId, 'sys_mm_cache_engine', 'Member menu cache engine (other than File option may require custom server setup)', 'File', 'select', 'File,EAccelerator,Memcache,APC,XCache', '', '', 25),
 (@iCategoryId, 'sys_page_cache_enable', 'Enable Page cache', '', 'checkbox', '', '', '', 26),
-(@iCategoryId, 'sys_page_cache_engine', 'Page cache engine (other than File option may require custom server setup)', 'File', 'select', 'File,EAccelerator,Memcache,APC,XCache', '', '', 27);
+(@iCategoryId, 'sys_page_cache_engine', 'Page cache engine (other than File option may require custom server setup)', 'File', 'select', 'File,EAccelerator,Memcache,APC,XCache', '', '', 27),
+(@iCategoryId, 'sys_oauth_key', 'BoonEx account key', '', 'digit', '', '', '', 28),
+(@iCategoryId, 'sys_oauth_secret', 'BoonEx account secret', '', 'digit', '', '', '', 29);
 
 
 --

+ 66 - 2
modules/boonex/english/data/langs/system.xml

@@ -1576,13 +1576,16 @@ Please click "Continue" below to navigate to the home page of the site.]]></stri
 	<string name="_adm_stg_cpt_category_languages"><![CDATA[Languages]]></string>
 	<string name="_adm_stg_err_save"><![CDATA[Cannot save settings]]></string>
 	<string name="_adm_stg_scs_save"><![CDATA[Settings were successfully saved]]></string>
+
+	<!-- Store -->
 	<string name="_adm_page_cpt_store"><![CDATA[Store]]></string>
 	<string name="_adm_wgt_cpt_store"><![CDATA[Store]]></string>
 	<string name="_adm_lmi_cpt_goodies"><![CDATA[Storefront]]></string>
 	<string name="_adm_lmi_cpt_featured"><![CDATA[Featured]]></string>
 	<string name="_adm_lmi_cpt_purchases"><![CDATA[Purchases]]></string>
 	<string name="_adm_lmi_cpt_updates"><![CDATA[Updates]]></string>
-	<string name="_adm_lmi_cpt_checkout"><![CDATA[Checkout]]></string>
+	<string name="_adm_lmi_cpt_checkout"><![CDATA[Checkout {checkout_counter}]]></string>
+	<string name="_adm_lmi_cpt_downloaded"><![CDATA[Downloaded]]></string>
 	<string name="_adm_block_cpt_last_featured"><![CDATA[Latest Featured]]></string>
 	<string name="_adm_action_cpt_see_all_featured"><![CDATA[See All Featured]]></string>
 	<string name="_adm_block_cpt_last_modules"><![CDATA[Latest Modules]]></string>
@@ -1591,10 +1594,67 @@ Please click "Continue" below to navigate to the home page of the site.]]></stri
 	<string name="_adm_action_cpt_see_all_languages"><![CDATA[See All Languages]]></string>
 	<string name="_adm_block_cpt_last_templates"><![CDATA[Latest Templates]]></string>
 	<string name="_adm_action_cpt_see_all_templates"><![CDATA[See All Templates]]></string>
+	<string name="_adm_block_cpt_featured"><![CDATA[Featured]]></string>
+	<string name="_adm_block_cpt_modules"><![CDATA[Modules]]></string>
+	<string name="_adm_block_cpt_templates"><![CDATA[Templates]]></string>
+	<string name="_adm_block_cpt_languages"><![CDATA[Languages]]></string>
 	<string name="_adm_block_cpt_purchases"><![CDATA[Purchases]]></string>
+	<string name="_adm_block_cpt_updates"><![CDATA[Updates]]></string>
+	<string name="_adm_block_cpt_checkout"><![CDATA[Checkout]]></string>
+	<string name="_adm_block_cpt_checkout_by_vendor_ccode"><![CDATA[Products from {0} ({1} {2})]]></string>
+	<string name="_adm_block_cpt_checkout_by_vendor_csign"><![CDATA[Products from {0} ({1}{2})]]></string>
+	<string name="_adm_action_cpt_checkout"><![CDATA[Checkout]]></string>
+	<string name="_adm_action_cpt_delete_all"><![CDATA[Delete All]]></string>
+	<string name="_adm_block_cpt_downloaded_modules"><![CDATA[Modules]]></string>
+	<string name="_adm_block_cpt_downloaded_updates"><![CDATA[Updates]]></string>
 	<string name="_adm_btn_install_submit"><![CDATA[Install]]></string>
 	<string name="_adm_btn_installed_submit"><![CDATA[Installed]]></string>
+	<string name="_adm_btn_purchase_submit"><![CDATA[Purchase]]></string>
+	<string name="_adm_btn_download_submit"><![CDATA[Download]]></string>
+	<string name="_adm_btn_delete_submit"><![CDATA[Delete]]></string>
+	<string name="_adm_btn_cancel_submit"><![CDATA[Cancel]]></string>
 	<string name="_adm_btn_update_submit"><![CDATA[Update]]></string>
+	<string name="_adm_str_txt_update_from_to"><![CDATA[{0} - {1}]]></string>
+	<string name="_adm_str_txt_price_ccode"><![CDATA[{0} {1}]]></string>
+	<string name="_adm_str_txt_price_csign"><![CDATA[{0}{1}]]></string>
+	<string name="_adm_str_txt_discount_ccode"><![CDATA[({0} {1} for Premium)]]></string>
+	<string name="_adm_str_txt_discount_csign"><![CDATA[({0}{1} for Premium)]]></string>
+	<string name="_adm_str_txt_items_by"><![CDATA[item(s) by]]></string>
+	<string name="_adm_str_txt_price_free"><![CDATA[Free]]></string>
+	<string name="_adm_str_cpt_files_file"><![CDATA[File]]></string>
+	<string name="_adm_str_cpt_files_version"><![CDATA[Version]]></string>
+	<string name="_adm_str_txt_pv_title"><![CDATA[Title]]></string>
+	<string name="_adm_str_txt_pv_vendor"><![CDATA[Vendor]]></string>
+	<string name="_adm_str_txt_pv_version"><![CDATA[Version]]></string>
+	<string name="_adm_str_txt_pv_price"><![CDATA[Price]]></string>
+	<string name="_adm_str_txt_pv_discount"><![CDATA[Discount]]></string>
+	<string name="_adm_str_txt_pv_discount_off_ccode"><![CDATA[{0}% ({1} {2}) off for Premium]]></string>
+	<string name="_adm_str_txt_pv_discount_off_csign"><![CDATA[{0}% ({1}{2}) off for Premium]]></string>
+	<string name="_adm_str_txt_pv_category"><![CDATA[Category]]></string>
+	<string name="_adm_str_txt_pv_tags"><![CDATA[Tags]]></string>
+	<string name="_adm_str_txt_pv_stats"><![CDATA[Stats]]></string>
+	<string name="_adm_str_txt_pv_stats_reviews"><![CDATA[{0} reviews]]></string>
+	<string name="_adm_str_txt_pv_stats_likes"><![CDATA[{0} likes]]></string>
+	<string name="_adm_str_txt_pv_stats_views"><![CDATA[{0} views]]></string>
+	<string name="_adm_str_txt_pv_created"><![CDATA[Created]]></string>
+	<string name="_adm_str_txt_pv_updated"><![CDATA[Updated]]></string>
+	<string name="_adm_str_txt_pv_description"><![CDATA[Description]]></string>
+	<string name="_adm_str_err_no_product_info"><![CDATA[Cannot load product info. Try again later.]]></string>
+	<string name="_adm_err_str_no_files"><![CDATA[Cannot get a list of downloadable packages attached to this product.]]></string>
+	<string name="_adm_str_err_download_failed"><![CDATA[Download faild. Please report.]]></string>
+	<string name="_adm_str_err_cannot_write"><![CDATA[Cannot save downloaded file.]]></string>
+	<string name="_adm_str_err_zip_not_available"><![CDATA[ZIP library is not available.]]></string>
+	<string name="_adm_str_err_cannot_unzip_package"><![CDATA[Cannot unarchive ZIP package.]]></string>
+	<string name="_adm_str_err_no_ftp_info"><![CDATA[FTP access info was not specified. You may enter it via Settings -> Other.]]></string>
+	<string name="_adm_str_err_cannot_connect_to_ftp"><![CDATA[Cannot connect to FTP server.]]></string>
+	<string name="_adm_str_err_destination_not_valid"><![CDATA[Destination FTP folder doesn't look like a valid Dolphin root folder.]]></string>
+	<string name="_adm_str_err_wrong_package_format"><![CDATA[Wrong package format.]]></string>
+	<string name="_adm_str_err_ftp_copy_failed"><![CDATA[FTP transfer failed, please check write permission for this FTP user.]]></string>
+	<string name="_adm_str_msg_download_successfully"><![CDATA[Module was successfully downloaded. You may install it via Store -> Downloaded.]]></string>
+	<string name="_adm_msg_oauth_need_authorize"><![CDATA[You need to to login into your <b>BoonEx</b> account to get data. Click <a href="{0}">here</a> to login.]]></string>
+	<string name="_adm_err_oauth_empty_key_secret"><![CDATA[You need to enter Key and Secret provided by <b>BoonEx</b> in Settings -> Other]]></string>
+	<string name="_adm_err_oauth_cannot_get_token"><![CDATA[Cannot connect to <b>BoonEx</b>. Please try again later.]]></string>
+	<string name="_adm_err_oauth_cannot_read_answer"><![CDATA[Cannot get a valid answer from <b>BoonEx</b>. Please try again later.]]></string>
 	<string name="_adm_act_err_failed_page_loading"><![CDATA[Cannot load the page]]></string>
 
     <!-- Extensions -->
@@ -2356,6 +2416,11 @@ Please click "Continue" below to navigate to the home page of the site.]]></stri
 	<string name="_adm_txt_modules_path_to_dolphin"><![CDATA[Path to Dolphin\'s folder]]></string>
 	<string name="_adm_err_modules_empty_path_to_dolphin"><![CDATA[Please enter path to Dolphin folder on your FTP server.]]></string>
 	<string name="_adm_btn_modules_upload"><![CDATA[Upload]]></string>
+	<string name="_adm_err_modules_cannot_add_to_cart"><![CDATA[Cannot add the product to shopping cart.]]></string>
+	<string name="_adm_err_modules_cannot_delete_from_cart"><![CDATA[Cannot delete the product(s) from shopping cart.]]></string>
+	<string name="_adm_err_modules_cannot_checkout_empty_vendor"><![CDATA[Cannot checkout. The vendor is unknown.]]></string>
+	<string name="_adm_msg_modules_success_added_to_cart"><![CDATA[The product was successfully added to shopping cart.]]></string>
+	<string name="_adm_msg_modules_success_checkouted"><![CDATA[The payment was successfully accepted and should be processed automatically in a few minutes. Then the purchased product(s) will be available in Store -> Purchases.<br />Note. If it doesn't happen for some reason, you need to contact with product's vendor and provide him with your payment's details. He'll be able to process payment manually if the payment was received.]]></string>
 	<string name="_adm_msg_modules_success_upload"><![CDATA[The package was uploaded successfully]]></string>
 	<string name="_adm_msg_modules_success_delete"><![CDATA[The package was deleted successfully]]></string>
 	<string name="_adm_msg_modules_success_install"><![CDATA[The package was installed successfully]]></string>
@@ -2422,7 +2487,6 @@ Please click "Continue" below to navigate to the home page of the site.]]></stri
 	<string name="_adm_err_modules_already_installed"><![CDATA[The module was already installed.]]></string>
 	<string name="_adm_txt_modules_params_used"><![CDATA[A module with similar unique parameters is already installed.]]></string>
 	<string name="_adm_txt_modules_operation_update"><![CDATA[Installation of update <b>{0}</b> from {1} up to {2}]]></string>
-	<string name="_adm_txt_modules_title_update"><![CDATA[<span class="modules-item-title">{0}</span> ({1} - {2})]]></string>
 	<string name="_adm_txt_modules_upload"><![CDATA[Add New Module]]></string>
 	<string name="_adm_txt_modules_market"><![CDATA[BoonEx Market]]></string>
 	<string name="_adm_txt_modules_updates"><![CDATA[Available Updates]]></string>

+ 1 - 0
modules/boonex/russian/data/langs/system.xml

@@ -1594,6 +1594,7 @@ Please click "Continue" below to navigate to the home page of the site.]]></stri
 	<string name="_adm_block_cpt_last_templates"><![CDATA[Latest Templates]]></string>
 	<string name="_adm_action_cpt_see_all_templates"><![CDATA[See All Templates]]></string>
 	<string name="_adm_block_cpt_purchases"><![CDATA[Purchases]]></string>
+	<string name="_adm_block_cpt_updates"><![CDATA[Updates]]></string>
 	<string name="_adm_btn_install_submit"><![CDATA[Install]]></string>
 	<string name="_adm_btn_installed_submit"><![CDATA[Installed]]></string>
 	<string name="_adm_btn_update_submit"><![CDATA[Update]]></string>

+ 1156 - 0
plugins/jquery/jquery.fancybox.js

@@ -0,0 +1,1156 @@
+/*
+ * FancyBox - jQuery Plugin
+ * Simple and fancy lightbox alternative
+ *
+ * Examples and documentation at: http://fancybox.net
+ *
+ * Copyright (c) 2008 - 2010 Janis Skarnelis
+ * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
+ *
+ * Version: 1.3.4 (11/11/2010)
+ * Requires: jQuery v1.3+
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ */
+
+;(function($) {
+	var tmp, loading, overlay, wrap, outer, content, close, title, nav_left, nav_right,
+
+		selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [],
+
+		ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i,
+
+		loadingTimer, loadingFrame = 1,
+
+		titleHeight = 0, titleStr = '', start_pos, final_pos, busy = false, fx = $.extend($('<div/>')[0], { prop: 0 }),
+
+		isIE6 = $.browser.msie && $.browser.version < 7 && !window.XMLHttpRequest,
+
+		/*
+		 * Private methods 
+		 */
+
+		_abort = function() {
+			loading.hide();
+
+			imgPreloader.onerror = imgPreloader.onload = null;
+
+			if (ajaxLoader) {
+				ajaxLoader.abort();
+			}
+
+			tmp.empty();
+		},
+
+		_error = function() {
+			if (false === selectedOpts.onError(selectedArray, selectedIndex, selectedOpts)) {
+				loading.hide();
+				busy = false;
+				return;
+			}
+
+			selectedOpts.titleShow = false;
+
+			selectedOpts.width = 'auto';
+			selectedOpts.height = 'auto';
+
+			tmp.html( '<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>' );
+
+			_process_inline();
+		},
+
+		_start = function() {
+			var obj = selectedArray[ selectedIndex ],
+				href, 
+				type, 
+				title,
+				str,
+				emb,
+				ret;
+
+			_abort();
+
+			selectedOpts = $.extend({}, $.fn.fancybox.defaults, (typeof $(obj).data('fancybox') == 'undefined' ? selectedOpts : $(obj).data('fancybox')));
+
+			ret = selectedOpts.onStart(selectedArray, selectedIndex, selectedOpts);
+
+			if (ret === false) {
+				busy = false;
+				return;
+			} else if (typeof ret == 'object') {
+				selectedOpts = $.extend(selectedOpts, ret);
+			}
+
+			title = selectedOpts.title || (obj.nodeName ? $(obj).attr('title') : obj.title) || '';
+
+			if (obj.nodeName && !selectedOpts.orig) {
+				selectedOpts.orig = $(obj).children("img:first").length ? $(obj).children("img:first") : $(obj);
+			}
+
+			if (title === '' && selectedOpts.orig && selectedOpts.titleFromAlt) {
+				title = selectedOpts.orig.attr('alt');
+			}
+
+			href = selectedOpts.href || (obj.nodeName ? $(obj).attr('href') : obj.href) || null;
+
+			if ((/^(?:javascript)/i).test(href) || href == '#') {
+				href = null;
+			}
+
+			if (selectedOpts.type) {
+				type = selectedOpts.type;
+
+				if (!href) {
+					href = selectedOpts.content;
+				}
+
+			} else if (selectedOpts.content) {
+				type = 'html';
+
+			} else if (href) {
+				if (href.match(imgRegExp)) {
+					type = 'image';
+
+				} else if (href.match(swfRegExp)) {
+					type = 'swf';
+
+				} else if ($(obj).hasClass("iframe")) {
+					type = 'iframe';
+
+				} else if (href.indexOf("#") === 0) {
+					type = 'inline';
+
+				} else {
+					type = 'ajax';
+				}
+			}
+
+			if (!type) {
+				_error();
+				return;
+			}
+
+			if (type == 'inline') {
+				obj	= href.substr(href.indexOf("#"));
+				type = $(obj).length > 0 ? 'inline' : 'ajax';
+			}
+
+			selectedOpts.type = type;
+			selectedOpts.href = href;
+			selectedOpts.title = title;
+
+			if (selectedOpts.autoDimensions) {
+				if (selectedOpts.type == 'html' || selectedOpts.type == 'inline' || selectedOpts.type == 'ajax') {
+					selectedOpts.width = 'auto';
+					selectedOpts.height = 'auto';
+				} else {
+					selectedOpts.autoDimensions = false;	
+				}
+			}
+
+			if (selectedOpts.modal) {
+				selectedOpts.overlayShow = true;
+				selectedOpts.hideOnOverlayClick = false;
+				selectedOpts.hideOnContentClick = false;
+				selectedOpts.enableEscapeButton = false;
+				selectedOpts.showCloseButton = false;
+			}
+
+			selectedOpts.padding = parseInt(selectedOpts.padding, 10);
+			selectedOpts.margin = parseInt(selectedOpts.margin, 10);
+
+			tmp.css('padding', (selectedOpts.padding + selectedOpts.margin));
+
+			$('.fancybox-inline-tmp').unbind('fancybox-cancel').bind('fancybox-change', function() {
+				$(this).replaceWith(content.children());				
+			});
+
+			switch (type) {
+				case 'html' :
+					tmp.html( selectedOpts.content );
+					_process_inline();
+				break;
+
+				case 'inline' :
+					if ( $(obj).parent().is('#fancybox-content') === true) {
+						busy = false;
+						return;
+					}
+
+					$('<div class="fancybox-inline-tmp" />')
+						.hide()
+						.insertBefore( $(obj) )
+						.bind('fancybox-cleanup', function() {
+							$(this).replaceWith(content.children());
+						}).bind('fancybox-cancel', function() {
+							$(this).replaceWith(tmp.children());
+						});
+
+					$(obj).appendTo(tmp);
+
+					_process_inline();
+				break;
+
+				case 'image':
+					busy = false;
+
+					$.fancybox.showActivity();
+
+					imgPreloader = new Image();
+
+					imgPreloader.onerror = function() {
+						_error();
+					};
+
+					imgPreloader.onload = function() {
+						busy = true;
+
+						imgPreloader.onerror = imgPreloader.onload = null;
+
+						_process_image();
+					};
+
+					imgPreloader.src = href;
+				break;
+
+				case 'swf':
+					selectedOpts.scrolling = 'no';
+
+					str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>';
+					emb = '';
+
+					$.each(selectedOpts.swf, function(name, val) {
+						str += '<param name="' + name + '" value="' + val + '"></param>';
+						emb += ' ' + name + '="' + val + '"';
+					});
+
+					str += '<embed src="' + href + '" type="application/x-shockwave-flash" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"' + emb + '></embed></object>';
+
+					tmp.html(str);
+
+					_process_inline();
+				break;
+
+				case 'ajax':
+					busy = false;
+
+					$.fancybox.showActivity();
+
+					selectedOpts.ajax.win = selectedOpts.ajax.success;
+
+					ajaxLoader = $.ajax($.extend({}, selectedOpts.ajax, {
+						url	: href,
+						data : selectedOpts.ajax.data || {},
+						error : function(XMLHttpRequest, textStatus, errorThrown) {
+							if ( XMLHttpRequest.status > 0 ) {
+								_error();
+							}
+						},
+						success : function(data, textStatus, XMLHttpRequest) {
+							var o = typeof XMLHttpRequest == 'object' ? XMLHttpRequest : ajaxLoader;
+							if (o.status == 200) {
+								if ( typeof selectedOpts.ajax.win == 'function' ) {
+									ret = selectedOpts.ajax.win(href, data, textStatus, XMLHttpRequest);
+
+									if (ret === false) {
+										loading.hide();
+										return;
+									} else if (typeof ret == 'string' || typeof ret == 'object') {
+										data = ret;
+									}
+								}
+
+								tmp.html( data );
+								_process_inline();
+							}
+						}
+					}));
+
+				break;
+
+				case 'iframe':
+					_show();
+				break;
+			}
+		},
+
+		_process_inline = function() {
+			var
+				w = selectedOpts.width,
+				h = selectedOpts.height;
+
+			if (w.toString().indexOf('%') > -1) {
+				w = parseInt( ($(window).width() - (selectedOpts.margin * 2)) * parseFloat(w) / 100, 10) + 'px';
+
+			} else {
+				w = w == 'auto' ? 'auto' : w + 'px';	
+			}
+
+			if (h.toString().indexOf('%') > -1) {
+				h = parseInt( ($(window).height() - (selectedOpts.margin * 2)) * parseFloat(h) / 100, 10) + 'px';
+
+			} else {
+				h = h == 'auto' ? 'auto' : h + 'px';	
+			}
+
+			tmp.wrapInner('<div style="width:' + w + ';height:' + h + ';overflow: ' + (selectedOpts.scrolling == 'auto' ? 'auto' : (selectedOpts.scrolling == 'yes' ? 'scroll' : 'hidden')) + ';position:relative;"></div>');
+
+			selectedOpts.width = tmp.width();
+			selectedOpts.height = tmp.height();
+
+			_show();
+		},
+
+		_process_image = function() {
+			selectedOpts.width = imgPreloader.width;
+			selectedOpts.height = imgPreloader.height;
+
+			$("<img />").attr({
+				'id' : 'fancybox-img',
+				'src' : imgPreloader.src,
+				'alt' : selectedOpts.title
+			}).appendTo( tmp );
+
+			_show();
+		},
+
+		_show = function() {
+			var pos, equal;
+
+			loading.hide();
+
+			if (wrap.is(":visible") && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) {
+				$.event.trigger('fancybox-cancel');
+
+				busy = false;
+				return;
+			}
+
+			busy = true;
+
+			$(content.add( overlay )).unbind();
+
+			$(window).unbind("resize.fb scroll.fb");
+			$(document).unbind('keydown.fb');
+
+			if (wrap.is(":visible") && currentOpts.titlePosition !== 'outside') {
+				wrap.css('height', wrap.height());
+			}
+
+			currentArray = selectedArray;
+			currentIndex = selectedIndex;
+			currentOpts = selectedOpts;
+
+			if (currentOpts.overlayShow) {
+				overlay.css({
+					'background-color' : currentOpts.overlayColor,
+					'opacity' : currentOpts.overlayOpacity,
+					'cursor' : currentOpts.hideOnOverlayClick ? 'pointer' : 'auto',
+					'height' : $(document).height()
+				});
+
+				if (!overlay.is(':visible')) {
+					if (isIE6) {
+						$('select:not(#fancybox-tmp select)').filter(function() {
+							return this.style.visibility !== 'hidden';
+						}).css({'visibility' : 'hidden'}).one('fancybox-cleanup', function() {
+							this.style.visibility = 'inherit';
+						});
+					}
+
+					overlay.show();
+				}
+			} else {
+				overlay.hide();
+			}
+
+			final_pos = _get_zoom_to();
+
+			_process_title();
+
+			if (wrap.is(":visible")) {
+				$( close.add( nav_left ).add( nav_right ) ).hide();
+
+				pos = wrap.position(),
+
+				start_pos = {
+					top	 : pos.top,
+					left : pos.left,
+					width : wrap.width(),
+					height : wrap.height()
+				};
+
+				equal = (start_pos.width == final_pos.width && start_pos.height == final_pos.height);
+
+				content.fadeTo(currentOpts.changeFade, 0.3, function() {
+					var finish_resizing = function() {
+						content.html( tmp.contents() ).fadeTo(currentOpts.changeFade, 1, _finish);
+					};
+
+					$.event.trigger('fancybox-change');
+
+					content
+						.empty()
+						.removeAttr('filter')
+						.css({
+							'border-width' : currentOpts.padding,
+							'width'	: final_pos.width - currentOpts.padding * 2,
+							'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2
+						});
+
+					if (equal) {
+						finish_resizing();
+
+					} else {
+						fx.prop = 0;
+
+						$(fx).animate({prop: 1}, {
+							 duration : currentOpts.changeSpeed,
+							 easing : currentOpts.easingChange,
+							 step : _draw,
+							 complete : finish_resizing
+						});
+					}
+				});
+
+				return;
+			}
+
+			wrap.removeAttr("style");
+
+			content.css('border-width', currentOpts.padding);
+
+			if (currentOpts.transitionIn == 'elastic') {
+				start_pos = _get_zoom_from();
+
+				content.html( tmp.contents() );
+
+				wrap.show();
+
+				if (currentOpts.opacity) {
+					final_pos.opacity = 0;
+				}
+
+				fx.prop = 0;
+
+				$(fx).animate({prop: 1}, {
+					 duration : currentOpts.speedIn,
+					 easing : currentOpts.easingIn,
+					 step : _draw,
+					 complete : _finish
+				});
+
+				return;
+			}
+
+			if (currentOpts.titlePosition == 'inside' && titleHeight > 0) {	
+				title.show();	
+			}
+
+			content
+				.css({
+					'width' : final_pos.width - currentOpts.padding * 2,
+					'height' : selectedOpts.autoDimensions ? 'auto' : final_pos.height - titleHeight - currentOpts.padding * 2
+				})
+				.html( tmp.contents() );
+
+			wrap
+				.css(final_pos)
+				.fadeIn( currentOpts.transitionIn == 'none' ? 0 : currentOpts.speedIn, _finish );
+		},
+
+		_format_title = function(title) {
+			if (title && title.length) {
+				if (currentOpts.titlePosition == 'float') {
+					return '<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">' + title + '</td><td id="fancybox-title-float-right"></td></tr></table>';
+				}
+
+				return '<div id="fancybox-title-' + currentOpts.titlePosition + '">' + title + '</div>';
+			}
+
+			return false;
+		},
+
+		_process_title = function() {
+			titleStr = currentOpts.title || '';
+			titleHeight = 0;
+
+			title
+				.empty()
+				.removeAttr('style')
+				.removeClass();
+
+			if (currentOpts.titleShow === false) {
+				title.hide();
+				return;
+			}
+
+			titleStr = $.isFunction(currentOpts.titleFormat) ? currentOpts.titleFormat(titleStr, currentArray, currentIndex, currentOpts) : _format_title(titleStr);
+
+			if (!titleStr || titleStr === '') {
+				title.hide();
+				return;
+			}
+
+			title
+				.addClass('fancybox-title-' + currentOpts.titlePosition)
+				.html( titleStr )
+				.appendTo( 'body' )
+				.show();
+
+			switch (currentOpts.titlePosition) {
+				case 'inside':
+					title
+						.css({
+							'width' : final_pos.width - (currentOpts.padding * 2),
+							'marginLeft' : currentOpts.padding,
+							'marginRight' : currentOpts.padding
+						});
+
+					titleHeight = title.outerHeight(true);
+
+					title.appendTo( outer );
+
+					final_pos.height += titleHeight;
+				break;
+
+				case 'over':
+					title
+						.css({
+							'marginLeft' : currentOpts.padding,
+							'width'	: final_pos.width - (currentOpts.padding * 2),
+							'bottom' : currentOpts.padding
+						})
+						.appendTo( outer );
+				break;
+
+				case 'float':
+					title
+						.css('left', parseInt((title.width() - final_pos.width - 40)/ 2, 10) * -1)
+						.appendTo( wrap );
+				break;
+
+				default:
+					title
+						.css({
+							'width' : final_pos.width - (currentOpts.padding * 2),
+							'paddingLeft' : currentOpts.padding,
+							'paddingRight' : currentOpts.padding
+						})
+						.appendTo( wrap );
+				break;
+			}
+
+			title.hide();
+		},
+
+		_set_navigation = function() {
+			if (currentOpts.enableEscapeButton || currentOpts.enableKeyboardNav) {
+				$(document).bind('keydown.fb', function(e) {
+					if (e.keyCode == 27 && currentOpts.enableEscapeButton) {
+						e.preventDefault();
+						$.fancybox.close();
+
+					} else if ((e.keyCode == 37 || e.keyCode == 39) && currentOpts.enableKeyboardNav && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA' && e.target.tagName !== 'SELECT') {
+						e.preventDefault();
+						$.fancybox[ e.keyCode == 37 ? 'prev' : 'next']();
+					}
+				});
+			}
+
+			if (!currentOpts.showNavArrows) { 
+				nav_left.hide();
+				nav_right.hide();
+				return;
+			}
+
+			if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex !== 0) {
+				nav_left.show();
+			}
+
+			if ((currentOpts.cyclic && currentArray.length > 1) || currentIndex != (currentArray.length -1)) {
+				nav_right.show();
+			}
+		},
+
+		_finish = function () {
+			if (!$.support.opacity) {
+				content.get(0).style.removeAttribute('filter');
+				wrap.get(0).style.removeAttribute('filter');
+			}
+
+			if (selectedOpts.autoDimensions) {
+				content.css('height', 'auto');
+			}
+
+			wrap.css('height', 'auto');
+
+			if (titleStr && titleStr.length) {
+				title.show();
+			}
+
+			if (currentOpts.showCloseButton) {
+				close.show();
+			}
+
+			_set_navigation();
+	
+			if (currentOpts.hideOnContentClick)	{
+				content.bind('click', $.fancybox.close);
+			}
+
+			if (currentOpts.hideOnOverlayClick)	{
+				overlay.bind('click', $.fancybox.close);
+			}
+
+			$(window).bind("resize.fb", $.fancybox.resize);
+
+			if (currentOpts.centerOnScroll) {
+				$(window).bind("scroll.fb", $.fancybox.center);
+			}
+
+			if (currentOpts.type == 'iframe') {
+				$('<iframe id="fancybox-frame" name="fancybox-frame' + new Date().getTime() + '" frameborder="0" hspace="0" ' + ($.browser.msie ? 'allowtransparency="true""' : '') + ' scrolling="' + selectedOpts.scrolling + '" src="' + currentOpts.href + '"></iframe>').appendTo(content);
+			}
+
+			wrap.show();
+
+			busy = false;
+
+			$.fancybox.center();
+
+			currentOpts.onComplete(currentArray, currentIndex, currentOpts);
+
+			_preload_images();
+		},
+
+		_preload_images = function() {
+			var href, 
+				objNext;
+
+			if ((currentArray.length -1) > currentIndex) {
+				href = currentArray[ currentIndex + 1 ].href;
+
+				if (typeof href !== 'undefined' && href.match(imgRegExp)) {
+					objNext = new Image();
+					objNext.src = href;
+				}
+			}
+
+			if (currentIndex > 0) {
+				href = currentArray[ currentIndex - 1 ].href;
+
+				if (typeof href !== 'undefined' && href.match(imgRegExp)) {
+					objNext = new Image();
+					objNext.src = href;
+				}
+			}
+		},
+
+		_draw = function(pos) {
+			var dim = {
+				width : parseInt(start_pos.width + (final_pos.width - start_pos.width) * pos, 10),
+				height : parseInt(start_pos.height + (final_pos.height - start_pos.height) * pos, 10),
+
+				top : parseInt(start_pos.top + (final_pos.top - start_pos.top) * pos, 10),
+				left : parseInt(start_pos.left + (final_pos.left - start_pos.left) * pos, 10)
+			};
+
+			if (typeof final_pos.opacity !== 'undefined') {
+				dim.opacity = pos < 0.5 ? 0.5 : pos;
+			}
+
+			wrap.css(dim);
+
+			content.css({
+				'width' : dim.width - currentOpts.padding * 2,
+				'height' : dim.height - (titleHeight * pos) - currentOpts.padding * 2
+			});
+		},
+
+		_get_viewport = function() {
+			return [
+				$(window).width() - (currentOpts.margin * 2),
+				$(window).height() - (currentOpts.margin * 2),
+				$(document).scrollLeft() + currentOpts.margin,
+				$(document).scrollTop() + currentOpts.margin
+			];
+		},
+
+		_get_zoom_to = function () {
+			var view = _get_viewport(),
+				to = {},
+				resize = currentOpts.autoScale,
+				double_padding = currentOpts.padding * 2,
+				ratio;
+
+			if (currentOpts.width.toString().indexOf('%') > -1) {
+				to.width = parseInt((view[0] * parseFloat(currentOpts.width)) / 100, 10);
+			} else {
+				to.width = currentOpts.width + double_padding;
+			}
+
+			if (currentOpts.height.toString().indexOf('%') > -1) {
+				to.height = parseInt((view[1] * parseFloat(currentOpts.height)) / 100, 10);
+			} else {
+				to.height = currentOpts.height + double_padding;
+			}
+
+			if (resize && (to.width > view[0] || to.height > view[1])) {
+				if (selectedOpts.type == 'image' || selectedOpts.type == 'swf') {
+					ratio = (currentOpts.width ) / (currentOpts.height );
+
+					if ((to.width ) > view[0]) {
+						to.width = view[0];
+						to.height = parseInt(((to.width - double_padding) / ratio) + double_padding, 10);
+					}
+
+					if ((to.height) > view[1]) {
+						to.height = view[1];
+						to.width = parseInt(((to.height - double_padding) * ratio) + double_padding, 10);
+					}
+
+				} else {
+					to.width = Math.min(to.width, view[0]);
+					to.height = Math.min(to.height, view[1]);
+				}
+			}
+
+			to.top = parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - to.height - 40) * 0.5)), 10);
+			to.left = parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - to.width - 40) * 0.5)), 10);
+
+			return to;
+		},
+
+		_get_obj_pos = function(obj) {
+			var pos = obj.offset();
+
+			pos.top += parseInt( obj.css('paddingTop'), 10 ) || 0;
+			pos.left += parseInt( obj.css('paddingLeft'), 10 ) || 0;
+
+			pos.top += parseInt( obj.css('border-top-width'), 10 ) || 0;
+			pos.left += parseInt( obj.css('border-left-width'), 10 ) || 0;
+
+			pos.width = obj.width();
+			pos.height = obj.height();
+
+			return pos;
+		},
+
+		_get_zoom_from = function() {
+			var orig = selectedOpts.orig ? $(selectedOpts.orig) : false,
+				from = {},
+				pos,
+				view;
+
+			if (orig && orig.length) {
+				pos = _get_obj_pos(orig);
+
+				from = {
+					width : pos.width + (currentOpts.padding * 2),
+					height : pos.height + (currentOpts.padding * 2),
+					top	: pos.top - currentOpts.padding - 20,
+					left : pos.left - currentOpts.padding - 20
+				};
+
+			} else {
+				view = _get_viewport();
+
+				from = {
+					width : currentOpts.padding * 2,
+					height : currentOpts.padding * 2,
+					top	: parseInt(view[3] + view[1] * 0.5, 10),
+					left : parseInt(view[2] + view[0] * 0.5, 10)
+				};
+			}
+
+			return from;
+		},
+
+		_animate_loading = function() {
+			if (!loading.is(':visible')){
+				clearInterval(loadingTimer);
+				return;
+			}
+
+			$('div', loading).css('top', (loadingFrame * -40) + 'px');
+
+			loadingFrame = (loadingFrame + 1) % 12;
+		};
+
+	/*
+	 * Public methods 
+	 */
+
+	$.fn.fancybox = function(options) {
+		if (!$(this).length) {
+			return this;
+		}
+
+		$(this)
+			.data('fancybox', $.extend({}, options, ($.metadata ? $(this).metadata() : {})))
+			.unbind('click.fb')
+			.bind('click.fb', function(e) {
+				e.preventDefault();
+
+				if (busy) {
+					return;
+				}
+
+				busy = true;
+
+				$(this).blur();
+
+				selectedArray = [];
+				selectedIndex = 0;
+
+				var rel = $(this).attr('rel') || '';
+
+				if (!rel || rel == '' || rel === 'nofollow') {
+					selectedArray.push(this);
+
+				} else {
+					selectedArray = $("a[rel=" + rel + "], area[rel=" + rel + "]");
+					selectedIndex = selectedArray.index( this );
+				}
+
+				_start();
+
+				return;
+			});
+
+		return this;
+	};
+
+	$.fancybox = function(obj) {
+		var opts;
+
+		if (busy) {
+			return;
+		}
+
+		busy = true;
+		opts = typeof arguments[1] !== 'undefined' ? arguments[1] : {};
+
+		selectedArray = [];
+		selectedIndex = parseInt(opts.index, 10) || 0;
+
+		if ($.isArray(obj)) {
+			for (var i = 0, j = obj.length; i < j; i++) {
+				if (typeof obj[i] == 'object') {
+					$(obj[i]).data('fancybox', $.extend({}, opts, obj[i]));
+				} else {
+					obj[i] = $({}).data('fancybox', $.extend({content : obj[i]}, opts));
+				}
+			}
+
+			selectedArray = jQuery.merge(selectedArray, obj);
+
+		} else {
+			if (typeof obj == 'object') {
+				$(obj).data('fancybox', $.extend({}, opts, obj));
+			} else {
+				obj = $({}).data('fancybox', $.extend({content : obj}, opts));
+			}
+
+			selectedArray.push(obj);
+		}
+
+		if (selectedIndex > selectedArray.length || selectedIndex < 0) {
+			selectedIndex = 0;
+		}
+
+		_start();
+	};
+
+	$.fancybox.showActivity = function() {
+		clearInterval(loadingTimer);
+
+		loading.show();
+		loadingTimer = setInterval(_animate_loading, 66);
+	};
+
+	$.fancybox.hideActivity = function() {
+		loading.hide();
+	};
+
+	$.fancybox.next = function() {
+		return $.fancybox.pos( currentIndex + 1);
+	};
+
+	$.fancybox.prev = function() {
+		return $.fancybox.pos( currentIndex - 1);
+	};
+
+	$.fancybox.pos = function(pos) {
+		if (busy) {
+			return;
+		}
+
+		pos = parseInt(pos);
+
+		selectedArray = currentArray;
+
+		if (pos > -1 && pos < currentArray.length) {
+			selectedIndex = pos;
+			_start();
+
+		} else if (currentOpts.cyclic && currentArray.length > 1) {
+			selectedIndex = pos >= currentArray.length ? 0 : currentArray.length - 1;
+			_start();
+		}
+
+		return;
+	};
+
+	$.fancybox.cancel = function() {
+		if (busy) {
+			return;
+		}
+
+		busy = true;
+
+		$.event.trigger('fancybox-cancel');
+
+		_abort();
+
+		selectedOpts.onCancel(selectedArray, selectedIndex, selectedOpts);
+
+		busy = false;
+	};
+
+	// Note: within an iframe use - parent.$.fancybox.close();
+	$.fancybox.close = function() {
+		if (busy || wrap.is(':hidden')) {
+			return;
+		}
+
+		busy = true;
+
+		if (currentOpts && false === currentOpts.onCleanup(currentArray, currentIndex, currentOpts)) {
+			busy = false;
+			return;
+		}
+
+		_abort();
+
+		$(close.add( nav_left ).add( nav_right )).hide();
+
+		$(content.add( overlay )).unbind();
+
+		$(window).unbind("resize.fb scroll.fb");
+		$(document).unbind('keydown.fb');
+
+		content.find('iframe').attr('src', isIE6 && /^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank');
+
+		if (currentOpts.titlePosition !== 'inside') {
+			title.empty();
+		}
+
+		wrap.stop();
+
+		function _cleanup() {
+			overlay.fadeOut('fast');
+
+			title.empty().hide();
+			wrap.hide();
+
+			$.event.trigger('fancybox-cleanup');
+
+			content.empty();
+
+			currentOpts.onClosed(currentArray, currentIndex, currentOpts);
+
+			currentArray = selectedOpts	= [];
+			currentIndex = selectedIndex = 0;
+			currentOpts = selectedOpts	= {};
+
+			busy = false;
+		}
+
+		if (currentOpts.transitionOut == 'elastic') {
+			start_pos = _get_zoom_from();
+
+			var pos = wrap.position();
+
+			final_pos = {
+				top	 : pos.top ,
+				left : pos.left,
+				width :	wrap.width(),
+				height : wrap.height()
+			};
+
+			if (currentOpts.opacity) {
+				final_pos.opacity = 1;
+			}
+
+			title.empty().hide();
+
+			fx.prop = 1;
+
+			$(fx).animate({ prop: 0 }, {
+				 duration : currentOpts.speedOut,
+				 easing : currentOpts.easingOut,
+				 step : _draw,
+				 complete : _cleanup
+			});
+
+		} else {
+			wrap.fadeOut( currentOpts.transitionOut == 'none' ? 0 : currentOpts.speedOut, _cleanup);
+		}
+	};
+
+	$.fancybox.resize = function() {
+		if (overlay.is(':visible')) {
+			overlay.css('height', $(document).height());
+		}
+
+		$.fancybox.center(true);
+	};
+
+	$.fancybox.center = function() {
+		var view, align;
+
+		if (busy) {
+			return;	
+		}
+
+		align = arguments[0] === true ? 1 : 0;
+		view = _get_viewport();
+
+		if (!align && (wrap.width() > view[0] || wrap.height() > view[1])) {
+			return;	
+		}
+
+		wrap
+			.stop()
+			.animate({
+				'top' : parseInt(Math.max(view[3] - 20, view[3] + ((view[1] - content.height() - 40) * 0.5) - currentOpts.padding)),
+				'left' : parseInt(Math.max(view[2] - 20, view[2] + ((view[0] - content.width() - 40) * 0.5) - currentOpts.padding))
+			}, typeof arguments[0] == 'number' ? arguments[0] : 200);
+	};
+
+	$.fancybox.init = function() {
+		if ($("#fancybox-wrap").length) {
+			return;
+		}
+
+		$('body').append(
+			tmp	= $('<div id="fancybox-tmp"></div>'),
+			loading	= $('<div id="fancybox-loading"><div></div></div>'),
+			overlay	= $('<div id="fancybox-overlay"></div>'),
+			wrap = $('<div id="fancybox-wrap"></div>')
+		);
+
+		outer = $('<div id="fancybox-outer"></div>')
+			.append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>')
+			.appendTo( wrap );
+
+		outer.append(
+			content = $('<div id="fancybox-content"></div>'),
+			close = $('<a id="fancybox-close"></a>'),
+			title = $('<div id="fancybox-title"></div>'),
+
+			nav_left = $('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),
+			nav_right = $('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>')
+		);
+
+		close.click($.fancybox.close);
+		loading.click($.fancybox.cancel);
+
+		nav_left.click(function(e) {
+			e.preventDefault();
+			$.fancybox.prev();
+		});
+
+		nav_right.click(function(e) {
+			e.preventDefault();
+			$.fancybox.next();
+		});
+
+		if ($.fn.mousewheel) {
+			wrap.bind('mousewheel.fb', function(e, delta) {
+				if (busy) {
+					e.preventDefault();
+
+				} else if ($(e.target).get(0).clientHeight == 0 || $(e.target).get(0).scrollHeight === $(e.target).get(0).clientHeight) {
+					e.preventDefault();
+					$.fancybox[ delta > 0 ? 'prev' : 'next']();
+				}
+			});
+		}
+
+		if (!$.support.opacity) {
+			wrap.addClass('fancybox-ie');
+		}
+
+		if (isIE6) {
+			loading.addClass('fancybox-ie6');
+			wrap.addClass('fancybox-ie6');
+
+			$('<iframe id="fancybox-hide-sel-frame" src="' + (/^https/i.test(window.location.href || '') ? 'javascript:void(false)' : 'about:blank' ) + '" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(outer);
+		}
+	};
+
+	$.fn.fancybox.defaults = {
+		padding : 10,
+		margin : 40,
+		opacity : false,
+		modal : false,
+		cyclic : false,
+		scrolling : 'auto',	// 'auto', 'yes' or 'no'
+
+		width : 560,
+		height : 340,
+
+		autoScale : true,
+		autoDimensions : true,
+		centerOnScroll : false,
+
+		ajax : {},
+		swf : { wmode: 'transparent' },
+
+		hideOnOverlayClick : true,
+		hideOnContentClick : false,
+
+		overlayShow : true,
+		overlayOpacity : 0.7,
+		overlayColor : '#777',
+
+		titleShow : true,
+		titlePosition : 'float', // 'float', 'outside', 'inside' or 'over'
+		titleFormat : null,
+		titleFromAlt : false,
+
+		transitionIn : 'fade', // 'elastic', 'fade' or 'none'
+		transitionOut : 'fade', // 'elastic', 'fade' or 'none'
+
+		speedIn : 300,
+		speedOut : 300,
+
+		changeSpeed : 300,
+		changeFade : 'fast',
+
+		easingIn : 'swing',
+		easingOut : 'swing',
+
+		showCloseButton	 : true,
+		showNavArrows : true,
+		enableEscapeButton : true,
+		enableKeyboardNav : true,
+
+		onStart : function(){},
+		onCancel : function(){},
+		onComplete : function(){},
+		onCleanup : function(){},
+		onClosed : function(){},
+		onError : function(){}
+	};
+
+	$(document).ready(function() {
+		$.fancybox.init();
+	});
+
+})(jQuery);

+ 521 - 0
plugins/jquery/jquery.migrate.js

@@ -0,0 +1,521 @@
+/*!
+ * jQuery Migrate - v1.2.1 - 2013-05-08
+ * https://github.com/jquery/jquery-migrate
+ * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors; Licensed MIT
+ */
+(function( jQuery, window, undefined ) {
+// See http://bugs.jquery.com/ticket/13335
+// "use strict";
+
+
+var warnedAbout = {};
+
+// List of warnings already given; public read only
+jQuery.migrateWarnings = [];
+
+// Set to true to prevent console output; migrateWarnings still maintained
+jQuery.migrateMute = true;
+
+// Show a message on the console so devs know we're active
+if ( !jQuery.migrateMute && window.console && window.console.log ) {
+	window.console.log("JQMIGRATE: Logging is active");
+}
+
+// Set to false to disable traces that appear with warnings
+if ( jQuery.migrateTrace === undefined ) {
+	jQuery.migrateTrace = true;
+}
+
+// Forget any warnings we've already given; public
+jQuery.migrateReset = function() {
+	warnedAbout = {};
+	jQuery.migrateWarnings.length = 0;
+};
+
+function migrateWarn( msg) {
+	var console = window.console;
+	if ( !warnedAbout[ msg ] ) {
+		warnedAbout[ msg ] = true;
+		jQuery.migrateWarnings.push( msg );
+		if ( console && console.warn && !jQuery.migrateMute ) {
+			console.warn( "JQMIGRATE: " + msg );
+			if ( jQuery.migrateTrace && console.trace ) {
+				console.trace();
+			}
+		}
+	}
+}
+
+function migrateWarnProp( obj, prop, value, msg ) {
+	if ( Object.defineProperty ) {
+		// On ES5 browsers (non-oldIE), warn if the code tries to get prop;
+		// allow property to be overwritten in case some other plugin wants it
+		try {
+			Object.defineProperty( obj, prop, {
+				configurable: true,
+				enumerable: true,
+				get: function() {
+					migrateWarn( msg );
+					return value;
+				},
+				set: function( newValue ) {
+					migrateWarn( msg );
+					value = newValue;
+				}
+			});
+			return;
+		} catch( err ) {
+			// IE8 is a dope about Object.defineProperty, can't warn there
+		}
+	}
+
+	// Non-ES5 (or broken) browser; just set the property
+	jQuery._definePropertyBroken = true;
+	obj[ prop ] = value;
+}
+
+if ( document.compatMode === "BackCompat" ) {
+	// jQuery has never supported or tested Quirks Mode
+	migrateWarn( "jQuery is not compatible with Quirks Mode" );
+}
+
+
+var attrFn = jQuery( "<input/>", { size: 1 } ).attr("size") && jQuery.attrFn,
+	oldAttr = jQuery.attr,
+	valueAttrGet = jQuery.attrHooks.value && jQuery.attrHooks.value.get ||
+		function() { return null; },
+	valueAttrSet = jQuery.attrHooks.value && jQuery.attrHooks.value.set ||
+		function() { return undefined; },
+	rnoType = /^(?:input|button)$/i,
+	rnoAttrNodeType = /^[238]$/,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	ruseDefault = /^(?:checked|selected)$/i;
+
+// jQuery.attrFn
+migrateWarnProp( jQuery, "attrFn", attrFn || {}, "jQuery.attrFn is deprecated" );
+
+jQuery.attr = function( elem, name, value, pass ) {
+	var lowerName = name.toLowerCase(),
+		nType = elem && elem.nodeType;
+
+	if ( pass ) {
+		// Since pass is used internally, we only warn for new jQuery
+		// versions where there isn't a pass arg in the formal params
+		if ( oldAttr.length < 4 ) {
+			migrateWarn("jQuery.fn.attr( props, pass ) is deprecated");
+		}
+		if ( elem && !rnoAttrNodeType.test( nType ) &&
+			(attrFn ? name in attrFn : jQuery.isFunction(jQuery.fn[name])) ) {
+			return jQuery( elem )[ name ]( value );
+		}
+	}
+
+	// Warn if user tries to set `type`, since it breaks on IE 6/7/8; by checking
+	// for disconnected elements we don't warn on $( "<button>", { type: "button" } ).
+	if ( name === "type" && value !== undefined && rnoType.test( elem.nodeName ) && elem.parentNode ) {
+		migrateWarn("Can't change the 'type' of an input or button in IE 6/7/8");
+	}
+
+	// Restore boolHook for boolean property/attribute synchronization
+	if ( !jQuery.attrHooks[ lowerName ] && rboolean.test( lowerName ) ) {
+		jQuery.attrHooks[ lowerName ] = {
+			get: function( elem, name ) {
+				// Align boolean attributes with corresponding properties
+				// Fall back to attribute presence where some booleans are not supported
+				var attrNode,
+					property = jQuery.prop( elem, name );
+				return property === true || typeof property !== "boolean" &&
+					( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+
+					name.toLowerCase() :
+					undefined;
+			},
+			set: function( elem, value, name ) {
+				var propName;
+				if ( value === false ) {
+					// Remove boolean attributes when set to false
+					jQuery.removeAttr( elem, name );
+				} else {
+					// value is true since we know at this point it's type boolean and not false
+					// Set boolean attributes to the same name and set the DOM property
+					propName = jQuery.propFix[ name ] || name;
+					if ( propName in elem ) {
+						// Only set the IDL specifically if it already exists on the element
+						elem[ propName ] = true;
+					}
+
+					elem.setAttribute( name, name.toLowerCase() );
+				}
+				return name;
+			}
+		};
+
+		// Warn only for attributes that can remain distinct from their properties post-1.9
+		if ( ruseDefault.test( lowerName ) ) {
+			migrateWarn( "jQuery.fn.attr('" + lowerName + "') may use property instead of attribute" );
+		}
+	}
+
+	return oldAttr.call( jQuery, elem, name, value );
+};
+
+// attrHooks: value
+jQuery.attrHooks.value = {
+	get: function( elem, name ) {
+		var nodeName = ( elem.nodeName || "" ).toLowerCase();
+		if ( nodeName === "button" ) {
+			return valueAttrGet.apply( this, arguments );
+		}
+		if ( nodeName !== "input" && nodeName !== "option" ) {
+			migrateWarn("jQuery.fn.attr('value') no longer gets properties");
+		}
+		return name in elem ?
+			elem.value :
+			null;
+	},
+	set: function( elem, value ) {
+		var nodeName = ( elem.nodeName || "" ).toLowerCase();
+		if ( nodeName === "button" ) {
+			return valueAttrSet.apply( this, arguments );
+		}
+		if ( nodeName !== "input" && nodeName !== "option" ) {
+			migrateWarn("jQuery.fn.attr('value', val) no longer sets properties");
+		}
+		// Does not return so that setAttribute is also used
+		elem.value = value;
+	}
+};
+
+
+var matched, browser,
+	oldInit = jQuery.fn.init,
+	oldParseJSON = jQuery.parseJSON,
+	// Note: XSS check is done below after string is trimmed
+	rquickExpr = /^([^<]*)(<[\w\W]+>)([^>]*)$/;
+
+// $(html) "looks like html" rule change
+jQuery.fn.init = function( selector, context, rootjQuery ) {
+	var match;
+
+	if ( selector && typeof selector === "string" && !jQuery.isPlainObject( context ) &&
+			(match = rquickExpr.exec( jQuery.trim( selector ) )) && match[ 0 ] ) {
+		// This is an HTML string according to the "old" rules; is it still?
+		if ( selector.charAt( 0 ) !== "<" ) {
+			migrateWarn("$(html) HTML strings must start with '<' character");
+		}
+		if ( match[ 3 ] ) {
+			migrateWarn("$(html) HTML text after last tag is ignored");
+		}
+		// Consistently reject any HTML-like string starting with a hash (#9521)
+		// Note that this may break jQuery 1.6.x code that otherwise would work.
+		if ( match[ 0 ].charAt( 0 ) === "#" ) {
+			migrateWarn("HTML string cannot start with a '#' character");
+			jQuery.error("JQMIGRATE: Invalid selector string (XSS)");
+		}
+		// Now process using loose rules; let pre-1.8 play too
+		if ( context && context.context ) {
+			// jQuery object as context; parseHTML expects a DOM object
+			context = context.context;
+		}
+		if ( jQuery.parseHTML ) {
+			return oldInit.call( this, jQuery.parseHTML( match[ 2 ], context, true ),
+					context, rootjQuery );
+		}
+	}
+	return oldInit.apply( this, arguments );
+};
+jQuery.fn.init.prototype = jQuery.fn;
+
+// Let $.parseJSON(falsy_value) return null
+jQuery.parseJSON = function( json ) {
+	if ( !json && json !== null ) {
+		migrateWarn("jQuery.parseJSON requires a valid JSON string");
+		return null;
+	}
+	return oldParseJSON.apply( this, arguments );
+};
+
+jQuery.uaMatch = function( ua ) {
+	ua = ua.toLowerCase();
+
+	var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+		/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+		/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+		/(msie) ([\w.]+)/.exec( ua ) ||
+		ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+		[];
+
+	return {
+		browser: match[ 1 ] || "",
+		version: match[ 2 ] || "0"
+	};
+};
+
+// Don't clobber any existing jQuery.browser in case it's different
+if ( !jQuery.browser ) {
+	matched = jQuery.uaMatch( navigator.userAgent );
+	browser = {};
+
+	if ( matched.browser ) {
+		browser[ matched.browser ] = true;
+		browser.version = matched.version;
+	}
+
+	// Chrome is Webkit, but Webkit is also Safari.
+	if ( browser.chrome ) {
+		browser.webkit = true;
+	} else if ( browser.webkit ) {
+		browser.safari = true;
+	}
+
+	jQuery.browser = browser;
+}
+
+// Warn if the code tries to get jQuery.browser
+migrateWarnProp( jQuery, "browser", jQuery.browser, "jQuery.browser is deprecated" );
+
+jQuery.sub = function() {
+	function jQuerySub( selector, context ) {
+		return new jQuerySub.fn.init( selector, context );
+	}
+	jQuery.extend( true, jQuerySub, this );
+	jQuerySub.superclass = this;
+	jQuerySub.fn = jQuerySub.prototype = this();
+	jQuerySub.fn.constructor = jQuerySub;
+	jQuerySub.sub = this.sub;
+	jQuerySub.fn.init = function init( selector, context ) {
+		if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+			context = jQuerySub( context );
+		}
+
+		return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+	};
+	jQuerySub.fn.init.prototype = jQuerySub.fn;
+	var rootjQuerySub = jQuerySub(document);
+	migrateWarn( "jQuery.sub() is deprecated" );
+	return jQuerySub;
+};
+
+
+// Ensure that $.ajax gets the new parseJSON defined in core.js
+jQuery.ajaxSetup({
+	converters: {
+		"text json": jQuery.parseJSON
+	}
+});
+
+
+var oldFnData = jQuery.fn.data;
+
+jQuery.fn.data = function( name ) {
+	var ret, evt,
+		elem = this[0];
+
+	// Handles 1.7 which has this behavior and 1.8 which doesn't
+	if ( elem && name === "events" && arguments.length === 1 ) {
+		ret = jQuery.data( elem, name );
+		evt = jQuery._data( elem, name );
+		if ( ( ret === undefined || ret === evt ) && evt !== undefined ) {
+			migrateWarn("Use of jQuery.fn.data('events') is deprecated");
+			return evt;
+		}
+	}
+	return oldFnData.apply( this, arguments );
+};
+
+
+var rscriptType = /\/(java|ecma)script/i,
+	oldSelf = jQuery.fn.andSelf || jQuery.fn.addBack;
+
+jQuery.fn.andSelf = function() {
+	migrateWarn("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()");
+	return oldSelf.apply( this, arguments );
+};
+
+// Since jQuery.clean is used internally on older versions, we only shim if it's missing
+if ( !jQuery.clean ) {
+	jQuery.clean = function( elems, context, fragment, scripts ) {
+		// Set context per 1.8 logic
+		context = context || document;
+		context = !context.nodeType && context[0] || context;
+		context = context.ownerDocument || context;
+
+		migrateWarn("jQuery.clean() is deprecated");
+
+		var i, elem, handleScript, jsTags,
+			ret = [];
+
+		jQuery.merge( ret, jQuery.buildFragment( elems, context ).childNodes );
+
+		// Complex logic lifted directly from jQuery 1.8
+		if ( fragment ) {
+			// Special handling of each script element
+			handleScript = function( elem ) {
+				// Check if we consider it executable
+				if ( !elem.type || rscriptType.test( elem.type ) ) {
+					// Detach the script and store it in the scripts array (if provided) or the fragment
+					// Return truthy to indicate that it has been handled
+					return scripts ?
+						scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+						fragment.appendChild( elem );
+				}
+			};
+
+			for ( i = 0; (elem = ret[i]) != null; i++ ) {
+				// Check if we're done after handling an executable script
+				if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+					// Append to fragment and handle embedded scripts
+					fragment.appendChild( elem );
+					if ( typeof elem.getElementsByTagName !== "undefined" ) {
+						// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+						jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+						// Splice the scripts into ret after their former ancestor and advance our index beyond them
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+						i += jsTags.length;
+					}
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var eventAdd = jQuery.event.add,
+	eventRemove = jQuery.event.remove,
+	eventTrigger = jQuery.event.trigger,
+	oldToggle = jQuery.fn.toggle,
+	oldLive = jQuery.fn.live,
+	oldDie = jQuery.fn.die,
+	ajaxEvents = "ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",
+	rajaxEvent = new RegExp( "\\b(?:" + ajaxEvents + ")\\b" ),
+	rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+	hoverHack = function( events ) {
+		if ( typeof( events ) !== "string" || jQuery.event.special.hover ) {
+			return events;
+		}
+		if ( rhoverHack.test( events ) ) {
+			migrateWarn("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'");
+		}
+		return events && events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+	};
+
+// Event props removed in 1.9, put them back if needed; no practical way to warn them
+if ( jQuery.event.props && jQuery.event.props[ 0 ] !== "attrChange" ) {
+	jQuery.event.props.unshift( "attrChange", "attrName", "relatedNode", "srcElement" );
+}
+
+// Undocumented jQuery.event.handle was "deprecated" in jQuery 1.7
+if ( jQuery.event.dispatch ) {
+	migrateWarnProp( jQuery.event, "handle", jQuery.event.dispatch, "jQuery.event.handle is undocumented and deprecated" );
+}
+
+// Support for 'hover' pseudo-event and ajax event warnings
+jQuery.event.add = function( elem, types, handler, data, selector ){
+	if ( elem !== document && rajaxEvent.test( types ) ) {
+		migrateWarn( "AJAX events should be attached to document: " + types );
+	}
+	eventAdd.call( this, elem, hoverHack( types || "" ), handler, data, selector );
+};
+jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ){
+	eventRemove.call( this, elem, hoverHack( types ) || "", handler, selector, mappedTypes );
+};
+
+jQuery.fn.error = function() {
+	var args = Array.prototype.slice.call( arguments, 0);
+	migrateWarn("jQuery.fn.error() is deprecated");
+	args.splice( 0, 0, "error" );
+	if ( arguments.length ) {
+		return this.bind.apply( this, args );
+	}
+	// error event should not bubble to window, although it does pre-1.7
+	this.triggerHandler.apply( this, args );
+	return this;
+};
+
+jQuery.fn.toggle = function( fn, fn2 ) {
+
+	// Don't mess with animation or css toggles
+	if ( !jQuery.isFunction( fn ) || !jQuery.isFunction( fn2 ) ) {
+		return oldToggle.apply( this, arguments );
+	}
+	migrateWarn("jQuery.fn.toggle(handler, handler...) is deprecated");
+
+	// Save reference to arguments for access in closure
+	var args = arguments,
+		guid = fn.guid || jQuery.guid++,
+		i = 0,
+		toggler = function( event ) {
+			// Figure out which function to execute
+			var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+			jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+			// Make sure that clicks stop
+			event.preventDefault();
+
+			// and execute the function
+			return args[ lastToggle ].apply( this, arguments ) || false;
+		};
+
+	// link all the functions, so any of them can unbind this click handler
+	toggler.guid = guid;
+	while ( i < args.length ) {
+		args[ i++ ].guid = guid;
+	}
+
+	return this.click( toggler );
+};
+
+jQuery.fn.live = function( types, data, fn ) {
+	migrateWarn("jQuery.fn.live() is deprecated");
+	if ( oldLive ) {
+		return oldLive.apply( this, arguments );
+	}
+	jQuery( this.context ).on( types, this.selector, data, fn );
+	return this;
+};
+
+jQuery.fn.die = function( types, fn ) {
+	migrateWarn("jQuery.fn.die() is deprecated");
+	if ( oldDie ) {
+		return oldDie.apply( this, arguments );
+	}
+	jQuery( this.context ).off( types, this.selector || "**", fn );
+	return this;
+};
+
+// Turn global events into document-triggered events
+jQuery.event.trigger = function( event, data, elem, onlyHandlers  ){
+	if ( !elem && !rajaxEvent.test( event ) ) {
+		migrateWarn( "Global events are undocumented and deprecated" );
+	}
+	return eventTrigger.call( this,  event, data, elem || document, onlyHandlers  );
+};
+jQuery.each( ajaxEvents.split("|"),
+	function( _, name ) {
+		jQuery.event.special[ name ] = {
+			setup: function() {
+				var elem = this;
+
+				// The document needs no shimming; must be !== for oldIE
+				if ( elem !== document ) {
+					jQuery.event.add( document, name + "." + jQuery.guid, function() {
+						jQuery.event.trigger( name, null, elem, true );
+					});
+					jQuery._data( this, name, jQuery.guid++ );
+				}
+				return false;
+			},
+			teardown: function() {
+				if ( this !== document ) {
+					jQuery.event.remove( document, name + "." + jQuery._data( this, name ) );
+				}
+				return false;
+			}
+		};
+	}
+);
+
+
+})( jQuery, window );

+ 134 - 0
studio/classes/BxDolStudioCart.php

@@ -0,0 +1,134 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+defined('BX_DOL') or die('hack attempt');
+
+class BxDolStudioCart {
+	public static $sIDiv = ':';
+	public static $sPDiv = '_';
+	private $sSessionKey = 'bx-std-str-cart';
+	private $bAllowAccumulate = false;
+
+    public function __construct() {
+    	if(isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+	public function __clone() {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+	static function getInstance() {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+        	$sClass = __CLASS__;
+            $GLOBALS['bxDolClasses'][__CLASS__] = new $sClass();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+	/**
+     * Conver items to array with necessary structure.
+     *
+     * @param string/array $mixed - string with cart items divided with (:) or an array of cart items.
+     * @return array with items.
+     */
+    static function items2array($mixed) {
+        $aResult = array();
+        if(empty($mixed))
+        	return $aResult;
+
+		$sClass = __CLASS__;
+        if(is_string($mixed))
+			$aItems = explode($sClass::$sIDiv, $mixed);
+        else if(is_array($mixed))
+			$aItems = $mixed;
+        else
+            $aItems = array();
+
+        foreach($aItems as $sItem) {
+            $aItem = explode($sClass::$sPDiv, $sItem);
+            $aResult[] = array('vendor' => $aItem[0], 'item_id' => $aItem[1], 'item_count' => $aItem[2]);
+        }
+
+        return $aResult;
+    }
+
+    public function add($sVendor, $iItemId, $iItemCount) {
+    	$sDiv = BxDolStudioCart::$sPDiv;
+    	$oSession = BxDolSession::getInstance();
+
+        $sCartItem = $sVendor . $sDiv . $iItemId . $sDiv . $iItemCount;
+        $sCartItems = $this->getItems();
+
+        if(strpos($sCartItems, $sVendor . $sDiv . $iItemId . $sDiv) !== false) {
+        	if($this->bAllowAccumulate)
+            	$sCartItems = preg_replace("'" . $sVendor . $sDiv . $iItemId . $sDiv . "([0-9])+'e", "'" . $sVendor . $sDiv . $iItemId . $sDiv ."' . (\\1 + " . $iItemCount . ")",  $sCartItems);
+        }
+        else
+            $sCartItems = empty($sCartItems) ? $sCartItem : $sCartItems . BxDolStudioCart::$sIDiv . $sCartItem;
+
+		$this->setItems($sCartItems);
+    }
+
+    public function delete($sVendor, $iItemId = 0) {
+		$sPattern = "'" . $sVendor . (!empty($iItemId) ? "_" . $iItemId : "_[0-9]+") . "_[0-9]+:?'";
+
+        $sCartItems = $this->getItems();
+        $sCartItems = trim(preg_replace($sPattern, "", $sCartItems), BxDolStudioCart::$sIDiv);
+        $this->setItems($sCartItems);
+    }
+
+	public function getCount($sVendor = '') {
+		if($sVendor == '') {
+			$sItems = $this->getItems();
+			$aItems = $this->items2array($sItems);
+			return count($aItems);
+		}
+
+    	$aVendors = $this->parseByVendor();
+    	if(!isset($aVendors[$sVendor]))
+    		return 0;
+
+    	return count($aVendors[$sVendor]);
+    }
+
+    public function getByVendor($sVendor) {
+    	$aVendors = $this->parseByVendor();
+    	if(!isset($aVendors[$sVendor]) || empty($aVendors[$sVendor]))
+    		return array();
+
+    	return $aVendors[$sVendor];
+    }
+
+    public function parseByVendor(){
+        $sItems = $this->getItems();
+        return $this->parseBy($this->items2array($sItems), 'vendor');
+    }
+
+    protected function getItems() {
+    	$oSession = BxDolSession::getInstance();
+        return $oSession->getValue($this->sSessionKey);
+    }
+
+    protected function setItems($sItems) {
+    	$oSession = BxDolSession::getInstance();
+    	$oSession->setValue($this->sSessionKey, $sItems);
+    }
+
+    protected function parseBy($aItems, $sKey) {
+        $aResult = array();
+        foreach($aItems as $aItem)
+            if(isset($aItem[$sKey]))
+                $aResult[$aItem[$sKey]][] = $aItem;
+
+        return $aResult;
+    }
+}
+/** @} */

+ 26 - 5
studio/classes/BxDolStudioInstaller.php

@@ -160,7 +160,7 @@ class BxDolStudioInstaller extends BxDolInstallerUtils {
         );
     }
 
-    function install($aParams, $bEnable = false) {
+    public function install($aParams, $bEnable = false) {
         //--- Check whether the module was already installed ---//
         if($this->oDb->isModule($this->_aConfig['home_uri']))
             return array(
@@ -250,7 +250,7 @@ class BxDolStudioInstaller extends BxDolInstallerUtils {
         return $aResult;
     }
 
-    function uninstall($aParams, $bDisable = false) {
+    public function uninstall($aParams, $bDisable = false) {
         //--- Check whether the module was already uninstalled ---//
         if(!$this->oDb->isModule($this->_aConfig['home_uri']))
             return array(
@@ -302,7 +302,28 @@ class BxDolStudioInstaller extends BxDolInstallerUtils {
         return $aResult;
     }
 
-    function recompile($aParams) {
+	public function delete() {
+		bx_import('BxDolFtp');
+        $oFtp = new BxDolFtp($_SERVER['HTTP_HOST'], getParam('sys_ftp_login'), getParam('sys_ftp_password'), getParam('sys_ftp_dir'));
+        if(!$oFtp->connect())
+        	return array(
+                'message' => _t('_adm_err_modules_cannot_connect_to_ftp'),
+                'result' => false
+            );
+
+		if(!$oFtp->delete('modules/' . $this->_aConfig['home_dir']))
+			return array(
+                'message' => _t('_adm_err_modules_cannot_remove_package'),
+                'result' => false
+            );
+
+        return array(
+        	'message' => '', //_t('_adm_msg_modules_success_delete'), 
+        	'result' => true
+        );
+    }
+
+    public function recompile($aParams) {
         bx_import('BxDolStudioLanguagesUtils');
         $oLanguages = BxDolStudioLanguagesUtils::getInstance();
 
@@ -324,7 +345,7 @@ class BxDolStudioInstaller extends BxDolInstallerUtils {
         return $aResult;
     }
 
-    function enable($aParams) {
+    public function enable($aParams) {
         $aModule = $this->oDb->getModuleByUri($this->_aConfig['home_uri']);
 
         //--- Check whether the module is installed ---//
@@ -353,7 +374,7 @@ class BxDolStudioInstaller extends BxDolInstallerUtils {
         return $aResult;
     }
 
-    function disable($aParams) {
+    public function disable($aParams) {
         $aModule = $this->oDb->getModuleByUri($this->_aConfig['home_uri']);
 
         //--- Check whether the module is installed ---//

+ 154 - 4
studio/classes/BxDolStudioInstallerUtils.php

@@ -11,7 +11,7 @@ defined('BX_DOL') or die('hack attempt');
 bx_import('BxDolInstallerUtils');
 
 class BxDolStudioInstallerUtils extends BxDolInstallerUtils {
-    function BxDolStudioInstallerUtils() {
+    public function BxDolStudioInstallerUtils() {
         if(isset($GLOBALS['bxDolClasses'][get_class($this)]))
             trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
 
@@ -29,17 +29,17 @@ class BxDolStudioInstallerUtils extends BxDolInstallerUtils {
     /**
      * Get singleton instance of the class
      */
-    function getInstance() {
+    static function getInstance() {
         if(!isset($GLOBALS['bxDolClasses'][__CLASS__]))
             $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolStudioInstallerUtils();
 
         return $GLOBALS['bxDolClasses'][__CLASS__];
     }
 
-    function perform($sDirectory, $sOperation, $aParams = array()) {
+    public function perform($sDirectory, $sOperation, $aParams = array()) {
         $sConfigFile = 'install/config.php';
         $sInstallerFile = 'install/installer.php';
-        $sInstallerClass = 'Installer';
+        $sInstallerClass = $sOperation == 'update' ? 'Updater' : 'Installer';
 
         $sPathConfig = BX_DIRECTORY_PATH_MODULES . $sDirectory . $sConfigFile;
         $sPathInstaller = BX_DIRECTORY_PATH_MODULES . $sDirectory . $sInstallerFile;
@@ -64,5 +64,155 @@ class BxDolStudioInstallerUtils extends BxDolInstallerUtils {
 
         return $aResult;
     }
+
+	public function loadModules() {
+    	$aModules = array();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+		$aInstalledPathes = $aInstalledInfo = array();
+        $this->getInstalledInfo($aInstalledPathes, $aInstalledInfo);
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if(($rHandleVendor = opendir($sPath)) !== false) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor)) 
+                    continue;
+
+                if(($rHandleModule = opendir($sPath . $sVendor)) !== false) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+						$sConfigPath = $sPath . $sVendor . '/' . $sModule . '/install/config.php';
+						$aModule = $this->getConfigModule($sConfigPath, $aInstalledPathes, $aInstalledInfo);
+						if(empty($aModule))
+							continue;
+
+						$aModules[$aModule['title']] = $aModule;
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aModules);
+        return $aModules;
+    }
+
+	public function loadUpdates() {
+    	$aUpdates = array();
+    	$oTemplate = BxDolStudioTemplate::getInstance();
+
+		$aInstalledPathes = $aInstalledInfo = array();
+        $this->getInstalledInfo($aInstalledPathes, $aInstalledInfo);
+
+        $sPath = BX_DIRECTORY_PATH_MODULES;
+        if($rHandleVendor = opendir($sPath)) {
+            while(($sVendor = readdir($rHandleVendor)) !== false) {
+                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor))
+                    continue;
+
+                if($rHandleModule = opendir($sPath . $sVendor . '/')) {
+                    while(($sModule = readdir($rHandleModule)) !== false) {
+                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
+                            continue;
+
+                        if($rHandleUpdate = @opendir($sPath . $sVendor . '/' . $sModule . '/updates/')) {
+                            while(($sUpdate = readdir($rHandleUpdate)) !== false) {
+                                if(!is_dir($sPath . $sVendor . '/' . $sModule . '/updates/' . $sUpdate) || substr($sUpdate, 0, 1) == '.')
+                                    continue;
+
+								$sConfigPathModule = $sPath . $sVendor . '/' . $sModule . '/install/config.php';
+                                $sConfigPathUpdate = $sPath . $sVendor . '/' . $sModule . '/updates/' . $sUpdate . '/install/config.php';
+                                $aUpdate = $this->getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledPathes, $aInstalledInfo);
+                                if(empty($aUpdate))
+                                	continue;
+
+                                $aUpdates[$aUpdate['title']] = $aUpdate;
+                            }
+                            closedir($rHandleUpdate);
+                        }
+                    }
+                    closedir($rHandleModule);
+                }
+            }
+            closedir($rHandleVendor);
+        }
+
+        ksort($aUpdates);
+        return $aUpdates;
+    }
+
+	protected function getConfigModule($sConfigPath, $aInstalledPathes = array(), $aInstalledInfo = array()) {
+		if(!file_exists($sConfigPath)) 
+			return array();
+
+		include($sConfigPath);
+        $sModulePath = $aConfig['home_dir'];
+        $sTitle = bx_process_output($aConfig['title']);
+
+        $bInstalled = !empty($aInstalledPathes) && in_array($sModulePath, $aInstalledPathes);
+        $bEnabled = $bInstalled && !empty($aInstalledInfo) && (int)$aInstalledInfo[$sModulePath]['enabled'] == 1;
+
+        $sLinkMarket = '';
+        if(isset($aConfig['product_url'])) {
+        	$aTmplVars = array(
+            	'vendor' => $aConfig['vendor'],
+				'version' => $aConfig['version'],
+                'uri' => $aConfig['home_uri'],
+                'title' => $aConfig['title']
+			);
+
+            $sLinkMarket = BxDolStudioTemplate::getInstance()->parseHtmlByContent(bx_html_attribute($aConfig['product_url']), $aTmplVars, array('{', '}'));
+		}
+
+		return array(
+        	'name' => isset($aConfig['name']) ? $aConfig['name'] : $aConfig['home_uri'],
+            'title' => $sTitle,
+            'vendor' => $aConfig['vendor'],
+            'version' => $aConfig['version'],
+            'uri' => $aConfig['home_uri'],
+            'dir' => $aConfig['home_dir'],
+            'note' => isset($aConfig['note']) ? bx_process_output($aConfig['note']) : '',
+            'link_market' => $sLinkMarket,
+            'installed' => $bInstalled,
+            'enabled' => $bInstalled && $bEnabled
+		);
+	}
+
+	protected function getConfigUpdate($sConfigPathModule, $sConfigPathUpdate, $aInstalledPathes = array(), $aInstalledInfo = array()) {
+		$aModule = $this->getConfigModule($sConfigPathModule, $aInstalledPathes, $aInstalledInfo);
+		if(empty($aModule) || !$aModule['installed'])
+			return array();
+
+		if(!file_exists($sConfigPathUpdate))
+			return array();
+
+		include($sConfigPathUpdate);
+        $sTitle = bx_process_output($aConfig['title']);
+
+		return array(
+        	'title' => $sTitle,
+            'vendor' => $aConfig['vendor'],
+            'version_from' => $aConfig['version_from'],
+            'version_to' => $aConfig['version_to'],
+            'dir' => $aConfig['home_dir'],
+            'module_name' => $aModule['name'],
+            'module_dir' => $aModule['dir'],
+            'module_link_market' => $aModule['link_market'],
+		);
+	}
+
+    private function getInstalledInfo(&$aInstalledPathes, &$aInstalledInfo) {
+    	bx_import('BxDolModuleDb');
+        $aModules = BxDolModuleDb::getInstance()->getModules();
+
+        $aInstalledInfo = array();
+        foreach($aModules as $aModule)
+            $aInstalledInfo[$aModule['path']] = $aModule;
+
+        $aInstalledPathes = array_keys($aInstalledInfo);
+    }
 }
 /** @} */

+ 45 - 0
studio/classes/BxDolStudioJson.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+defined('BX_DOL') or die('hack attempt');
+
+class BxDolStudioJson {
+    public function __construct() {
+    	if(isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+    }
+
+	public function __clone() {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+	static function getInstance() {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+            $GLOBALS['bxDolClasses'][__CLASS__] = new BxDolStudioJson();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+    public function load($sUrl, $aParams = array()) {
+    	$sContent = bx_file_get_contents($sUrl, $aParams);
+    	if(empty($sContent))
+    		return false;
+
+		//--- Uncomment to debug
+		//echo $sContent; exit;
+		$mixedResult = json_decode($sContent, true);
+		if(is_null($mixedResult))
+			return false;
+
+		return $mixedResult;
+    }
+}
+/** @} */

+ 28 - 6
studio/classes/BxDolStudioLanguagesUtils.php

@@ -65,6 +65,31 @@ class BxDolStudioLanguagesUtils extends BxDolLanguages {
         return $aLanguages;
     }
 
+    function readLanguage($sPath, $sType = 'restore') {
+    	if(!file_exists($sPath))
+    		return array();
+
+    	$oXmlParser = BxDolXmlParser::getInstance();
+    	$sXmlContent = file_get_contents($sPath);
+
+    	$aResult = array();
+		$aResult['name'] = $oXmlParser->getAttribute($sXmlContent, 'resources', 'name');
+
+    	switch($sType) {
+    		case 'restore':
+    			$aResult['strings'] = $oXmlParser->getValues($sXmlContent, 'string');
+    			break;
+
+    		case 'update':
+    			$aResult['strings_del'] = $oXmlParser->getValues($sXmlContent, 'string_del');
+    			$aResult['strings_add'] = $oXmlParser->getValues($sXmlContent, 'string_add');
+    			$aResult['strings_upd'] = $oXmlParser->getValues($sXmlContent, 'string_upd');
+    			break;
+    	}
+
+    	return $aResult;
+    }
+
     function compileLanguage($mixedLang = 0, $bForce = false) {
         $sType = 'all';
         if(!empty($mixedLang)) {
@@ -379,11 +404,8 @@ class BxDolStudioLanguagesUtils extends BxDolLanguages {
                 return true;
         }
 
-        $sXmlContent = file_get_contents($sPath);
-        $sName = $oXmlParser->getAttribute($sXmlContent, 'resources', 'name');
-        $aStrings = $oXmlParser->getValues($sXmlContent, 'string');
-
-        if(empty($sName) || empty($aStrings) || $sName != $aLanguage['name'])
+        $aLanguageInfo = $this->readLanguage($sPath);
+        if(empty($aLanguageInfo['name']) || empty($aLanguageInfo['strings']) || $aLanguageInfo['name'] != $aLanguage['name'])
             return false;
 
         $aCategory = array();
@@ -394,7 +416,7 @@ class BxDolStudioLanguagesUtils extends BxDolLanguages {
         $this->oDb->deleteStringsBy(array('type' => 'by_cat_and_lang', 'category_id' => $aCategory['id'], 'language_id' => $aLanguage['id']));
 
         $bResult = true;
-        foreach($aStrings as $sKey => $sValue)
+        foreach($aLanguageInfo['strings'] as $sKey => $sValue)
             if($sKey != '')
                 $bResult &= $this->addLanguageString($sKey, $sValue, $aLanguage['id'], $aCategory['id'], false) > 0;
 

+ 192 - 0
studio/classes/BxDolStudioOAuth.php

@@ -0,0 +1,192 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinStudio Dolphin Studio
+ * @{
+ */
+defined('BX_DOL') or die('hack attempt');
+
+class BxDolStudioOAuth {
+	protected $sKey;
+	protected $sSecret;
+
+	protected $sErrorCode;
+	protected $sErrorMessage;
+
+	protected $oSession;
+
+    public function __construct() {
+    	if(isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error ('Multiple instances are not allowed for the class: ' . get_class($this), E_USER_ERROR);
+
+		$this->sKey = getParam('sys_oauth_key');
+		$this->sSecret = getParam('sys_oauth_secret');
+
+    	$this->sErrorCode = 'oauth_err_code';
+		$this->sErrorMessage = 'oauth_err_message';
+
+		bx_import('BxDolSession');
+		$this->oSession = BxDolSession::getInstance();
+    }
+
+	public function __clone() {
+        if (isset($GLOBALS['bxDolClasses'][get_class($this)]))
+            trigger_error('Clone is not allowed for the class: ' . get_class($this), E_USER_ERROR);
+    }
+
+	static function getInstance() {
+        if(!isset($GLOBALS['bxDolClasses'][__CLASS__])) {
+        	$sClass = __CLASS__;
+            $GLOBALS['bxDolClasses'][__CLASS__] = new $sClass();
+        }
+
+        return $GLOBALS['bxDolClasses'][__CLASS__];
+    }
+
+	static function isAuthorizedClient() {
+		$sClass = __CLASS__;
+    	return $sClass::getInstance()->isAuthorized();
+    }
+
+    static function getAuthorizedClient() {
+    	$sClass = __CLASS__;
+    	return $sClass::getInstance()->getAuthorizedUser();
+    }
+
+	public function loadItems($aParams = array()) {
+        if(empty($this->sKey) || empty($this->sSecret)) 
+        	return _t('_adm_err_oauth_empty_key_secret');
+
+		$mixedResult = $this->authorize($this->sKey, $this->sSecret);
+		if($mixedResult !== true)
+			return $mixedResult;
+
+		$aItems = $this->fetch($this->sKey, $this->sSecret, $aParams);
+		if(is_null($aItems))
+			return _t('_adm_err_oauth_cannot_read_answer');
+		else if(empty($aItems))
+			return MsgBox(_t('_Empty'));
+
+		if($this->isServerError($aItems))
+			return $this->processServerError($aItems);
+
+		return $aItems;
+    }
+
+    protected function isAuthorized() {
+		return (int)$this->oSession->getValue('sys_oauth_authorized') == 1;
+    }
+
+	protected function getAuthorizedUser() {
+		if(!$this->isAuthorized())
+			return 0;
+
+		return (int)$this->oSession->getValue('sys_oauth_authorized_user');
+    }
+
+    protected function authorize($sKey, $sSecret) {
+    	if($this->isAuthorized())
+			return true;
+
+		try {
+			$oConsumer = new OAuth($sKey, $sSecret);
+			$oConsumer->setAuthType(OAUTH_AUTH_TYPE_URI);
+			$oConsumer->enableDebug();
+
+			$bToken = bx_get('oauth_token') !== false;
+			$mixedSecret = $this->oSession->getValue('sys_oauth_secret');
+			if(!$bToken && $mixedSecret !== false) {
+				$this->oSession->unsetValue('sys_oauth_secret');
+				$mixedSecret = false;
+			}
+
+			//--- Get request token and redirect to authorize. 
+			if(!$bToken && $mixedSecret === false) {
+			    $aRequestToken = $oConsumer->getRequestToken(BX_DOL_OAUTH_URL_REQUEST_TOKEN);			    
+			    if(empty($aRequestToken))
+			    	return _t('_adm_err_oauth_cannot_get_token');
+
+			    if($this->isServerError($aRequestToken))
+			    	return $this->processServerError($aRequestToken);
+
+			    $this->oSession->setValue('sys_oauth_secret', $aRequestToken['oauth_token_secret']);
+			    return _t('_adm_msg_oauth_need_authorize', bx_append_url_params(BX_DOL_OAUTH_URL_AUTHORIZE, array('oauth_token' => $aRequestToken['oauth_token'], 'sid' => generateSid())));
+			}
+
+			//--- Get access token. 
+			if($bToken && $mixedSecret !== false) {
+			    $oConsumer->setToken(bx_get('oauth_token'), $mixedSecret);
+			    $aAccessToken = $oConsumer->getAccessToken(bx_append_url_params(BX_DOL_OAUTH_URL_ACCESS_TOKEN, array('oauth_verifier' => bx_get('oauth_verifier'))));
+			    if(empty($aAccessToken))
+			    	return _t('_adm_err_oauth_cannot_get_token');
+
+				if($this->isServerError($aAccessToken))
+			    	return $this->processServerError($aAccessToken);
+
+			    $this->oSession->setValue('sys_oauth_token', $aAccessToken['oauth_token']);
+			    $this->oSession->setValue('sys_oauth_secret', $aAccessToken['oauth_token_secret']);
+			    $this->oSession->setValue('sys_oauth_authorized', 1);
+			    $this->oSession->setValue('sys_oauth_authorized_user', (int)bx_get('oauth_user'));
+
+			    return true;
+			}
+		}
+		catch(OAuthException $e) {
+			//TODO: Write in LOG print_r($e)
+			return _t('_adm_err_oauth_cannot_get_token');
+		}
+    }
+
+    protected function fetch($sKey, $sSecret, $aParams = array()) {
+		if(!$this->isAuthorized())
+			return array();
+
+		try {
+			$oConsumer = new OAuth($sKey, $sSecret);
+			$oConsumer->setAuthType(OAUTH_AUTH_TYPE_URI);
+			$oConsumer->enableDebug();
+
+			$oConsumer->setToken($this->oSession->getValue('sys_oauth_token'), $this->oSession->getValue('sys_oauth_secret'));
+			$oConsumer->fetch(BX_DOL_OAUTH_URL_FETCH_DATA, $aParams, OAUTH_HTTP_METHOD_POST);
+
+			//--- Uncomment to debug
+			//echo $oConsumer->getLastResponse(); exit;
+			return json_decode($oConsumer->getLastResponse(), true);
+		}
+    	catch(OAuthException $e) {
+    		//TODO: Write in LOG print_r($e);
+			return array();
+		}
+    }
+
+    protected function isServerError($aResult) {
+    	return isset($aResult[$this->sErrorCode]) && isset($aResult[$this->sErrorMessage]);
+    }
+
+    protected function processServerError($aResult) {
+    	$iCode = $aResult[$this->sErrorCode];
+    	$sMessage = $aResult[$this->sErrorMessage];
+
+    	switch($iCode) {
+    		case '8':
+    		case '16':
+    		case '32':
+    		case '64':
+    		case '256':
+    		case '1024':
+    		case '2048':
+    			bx_import('BxDolSession');
+    			$this->oSession = BxDolSession::getInstance();
+    			$this->oSession->unsetValue('sys_oauth_token');
+			    $this->oSession->unsetValue('sys_oauth_secret');
+			    $this->oSession->unsetValue('sys_oauth_authorized');
+			    $this->oSession->unsetValue('sys_oauth_authorized_user');
+			    break;
+    	}
+
+    	return $sMessage;
+    }
+}
+/** @} */

+ 293 - 83
studio/classes/BxDolStudioStore.php

@@ -15,36 +15,117 @@ define('BX_DOL_STUDIO_STR_TYPE_DEFAULT', 'goodies');
 
 class BxDolStudioStore extends BxTemplStudioPage {
     protected $sPage;
-    protected $aProducts;
+    protected $aContent;
+
+    protected $iClient;
+
+    protected $aAlias;
 
     function BxDolStudioStore($sPage = "") {
         parent::BxTemplStudioPage('store');
 
         $this->oDb = new BxDolStudioStoreQuery();
 
+        $this->aAlias = array(
+        	'tag' => array(
+        		'modules' => 'extensions',
+        		'languages' => 'translations'
+        	),
+        	'category' => array()
+        );
+
         $this->sPage = BX_DOL_STUDIO_STR_TYPE_DEFAULT;
         if(is_string($sPage) && !empty($sPage))
             $this->sPage = $sPage;
 
+		bx_import('BxDolStudioOAuth');
+        $this->iClient = BxDolStudioOAuth::getAuthorizedClient();
+
         //--- Check actions ---//
         if(($sAction = bx_get('str_action')) !== false) {
 	        $sAction = bx_process_input($sAction);
 
             $aResult = array('code' => 1, 'message' => _t('_adm_mod_err_cannot_process_action'));
 	        switch($sAction) {
+	        	/*
+	        	 * NOTE. Is needed for download popup selector. Isn't used for now.
+	        	 */
+	        	case 'get-files':
+			    	$iId = (int)bx_get('str_id');
+			    	$sType = bx_process_input(bx_get('str_type'));
+			        $aResult = $this->getFiles($iId, $sType);
+			    	break;
+
+	        	case 'get-file':
+	        		$iId = (int)bx_get('str_id');
+	        		$aResult = $this->getFile($iId);
+	        		break;
+
+				case 'get-product':
+	        		$iId = (int)bx_get('str_id');
+	        		$aResult = $this->getProduct($iId);
+	        		break;
+
 	            case 'get-products-by-type':
-	                $sValue = bx_process_input(bx_get('str_value'));
-
-	                $sMethod = 'load' . ucfirst($sValue);
-	                if(method_exists($this, $sMethod)) {
-	                    $this->sPage = $sValue;
-		                $this->aProducts = $this->$sMethod();
-                        $aResult = array('code' => 0, 'content' => $this->getPageCode());
-	                }
-	                else 
-	                    $aResult = array('code' => 1, 'message' => _t('_adm_act_err_failed_page_loading'));
+	                $this->sPage = bx_process_input(bx_get('str_value'));
+
+	                $sContent = $this->getPageCode();
+	                if(!empty($sContent))
+						$aResult = array('code' => 0, 'content' => $sContent);
+					else
+						$aResult = array('code' => 1, 'message' => _t('_adm_act_err_failed_page_loading'));
 	                break;
 
+	            case 'get-products-by-page':
+	            	$this->sPage = bx_process_input(bx_get('str_type'));
+
+	            	$sContent = $this->getPageContent();
+	            	if(!empty($sContent))
+	            		$aResult = array('code' => 0, 'content' => $sContent);
+	            	else 
+	            		$aResult = array('code' => 1, 'message' => _t('_adm_act_err_failed_page_loading'));
+	            	break;
+
+	            case 'add-to-cart':
+	            	$sVendor = bx_process_input(bx_get('str_vendor'));
+	            	$iItem = (int)bx_get('str_item');
+	            	$iItemCount = 1;
+
+	            	if(empty($sVendor) || empty($iItem)) {
+	            		$aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_add_to_cart'));
+	            		break;
+	            	}
+
+	            	bx_import('BxDolStudioCart');
+	            	BxDolStudioCart::getInstance()->add($sVendor, $iItem, $iItemCount);
+	            	$aResult = array('code' => 0, 'message' => _t('_adm_msg_modules_success_added_to_cart'));
+	            	break;
+
+	            case 'delete-from-cart':
+	            	$sVendor = bx_process_input(bx_get('str_vendor'));
+	            	$iItem = (int)bx_get('str_item');
+
+		        	if(empty($sVendor)) {
+	            		$aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_delete_from_cart'));
+	            		break;
+	            	}
+
+	            	bx_import('BxDolStudioCart');
+	            	BxDolStudioCart::getInstance()->delete($sVendor, $iItem);
+	            	$aResult = array('code' => 0, 'message' => '');
+	            	break;
+
+	            case 'checkout-cart':
+	            	$sVendor = bx_process_input(bx_get('str_vendor'));
+	        		if(empty($sVendor)) {
+	            		$aResult = array('code' => 1, 'message' => _t('_adm_err_modules_cannot_checkout_empty_vendor'));
+	            		break;
+	            	}
+
+	            	$sLocation = $this->checkoutCart($sVendor);
+					$aResult = array('code' => 0, 'message' => '', 'redirect' => $sLocation);
+	            	break;
+
 	        	case 'install':
 	        	    $sValue = bx_process_input(bx_get('str_value'));
 	        	    if(empty($sValue))
@@ -53,14 +134,32 @@ class BxDolStudioStore extends BxTemplStudioPage {
                     bx_import('BxDolStudioInstallerUtils');
 	        		$aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'install');
 	        		break;
+
+				case 'update':
+	        	    $sValue = bx_process_input(bx_get('str_value'));
+	        	    if(empty($sValue))
+	                    break;
+
+                    bx_import('BxDolStudioInstallerUtils');
+	        		$aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'update');
+	        		break;
+
+				case 'delete':
+	        	    $sValue = bx_process_input(bx_get('str_value'));
+	        	    if(empty($sValue))
+	                    break;
+
+                    bx_import('BxDolStudioInstallerUtils');
+	        		$aResult = BxDolStudioInstallerUtils::getInstance()->perform($sValue, 'delete');
+	        		break;
 	        }
 
 	        if(!empty($aResult['message'])) {
                 bx_import('BxDolStudioTemplate');
-                $sContent = BxDolStudioTemplate::getInstance()->parseHtmlByName('mod_action_result.html', array('content' => $aResult['message']));
+                $aResult['message'] = BxDolStudioTemplate::getInstance()->parseHtmlByName('mod_action_result.html', array('content' => $aResult['message']));
 
                 bx_import('BxTemplStudioFunctions');
-                $aResult['message'] = BxTemplStudioFunctions::getInstance()->transBox($sContent);
+                $aResult['message'] = BxTemplStudioFunctions::getInstance()->transBox($aResult['message']);
             }
 
 	        $oJson = new Services_JSON();		        
@@ -70,25 +169,30 @@ class BxDolStudioStore extends BxTemplStudioPage {
     }
 
     protected function loadGoodies() {
+    	$iPerPage = 5;
         $aProducts = array();
         $sJsObject = $this->getPageJsObject();
 
+        bx_import('BxDolStudioJson');
+        $oJson = BxDolStudioJson::getInstance();
+
         // Load featured
         $aProducts[] = array(
             'caption' => '_adm_block_cpt_last_featured',
             'actions' => array(
                 array('name' => 'featured', 'caption' => '_adm_action_cpt_see_all_featured', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('featured', this)")
             ), 
-            'items' => array()
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_featured', array('start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
         );
 
+        
         // Load modules
         $aProducts[] = array(
             'caption' => '_adm_block_cpt_last_modules',
             'actions' => array(
                 array('name' => 'modules', 'caption' => '_adm_action_cpt_see_all_modules', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('modules', this)")
             ), 
-            'items' => array()
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'extensions', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
         );
 
         // Load templates
@@ -97,7 +201,7 @@ class BxDolStudioStore extends BxTemplStudioPage {
             'actions' => array(
                 array('name' => 'templates', 'caption' => '_adm_action_cpt_see_all_templates', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('templates', this)")
             ), 
-            'items' => array()
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'templates', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
         );
 
         // Load languages
@@ -106,79 +210,185 @@ class BxDolStudioStore extends BxTemplStudioPage {
             'actions' => array(
                 array('name' => 'languages', 'caption' => '_adm_action_cpt_see_all_languages', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".changePage('languages', this)")
             ), 
-            'items' => array()
+            'items' => $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => 'translations', 'start' => 0, 'per_page' => $iPerPage, 'client' => $this->iClient))
         );
-                
+
         return $aProducts;
     }
 
-    protected function loadPurchases() {
-        $aProducts = array();
-        $oTemplate = BxDolStudioTemplate::getInstance();
-
-        $aInstalled = $this->oDb->getModules();
-        $aInstalledInfo = array();
-        foreach($aInstalled as $aModule)
-            $aInstalledInfo[$aModule['path']] = $aModule;
-        $aInstalledPathes = array_keys($aInstalledInfo);
-
-        $iOrder = 0;
-        $sPath = BX_DIRECTORY_PATH_ROOT . 'modules/';
-        if(($rHandleVendor = opendir($sPath)) !== false) {
-            while(($sVendor = readdir($rHandleVendor)) !== false) {
-                if(substr($sVendor, 0, 1) == '.' || !is_dir($sPath . $sVendor)) 
-                    continue;
-
-                if(($rHandleModule = opendir($sPath . $sVendor)) !== false) {
-                    while(($sModule = readdir($rHandleModule)) !== false) {
-                        if(!is_dir($sPath . $sVendor . '/' . $sModule) || substr($sModule, 0, 1) == '.')
-                            continue;
-
-                        $sConfigPath = $sPath . $sVendor . '/' . $sModule . '/install/config.php';
-                        if(!file_exists($sConfigPath)) 
-                            continue;
-
-                        include($sConfigPath);
-                        $sModulePath = $aConfig['home_dir'];
-                        $sTitle = bx_process_output($aConfig['title']);
-
-                        $bInstalled = in_array($sModulePath, $aInstalledPathes);
-                        $bEnabled = $bInstalled && (int)$aInstalledInfo[$sModulePath]['enabled'] == 1;
-
-                        $sLinkMarket = '';
-                        if(isset($aConfig['product_url'])) {
-                            $aTmplVars = array(
-                            	'vendor' => $aConfig['vendor'],
-                            	'version' => $aConfig['version'],
-                                'uri' => $aConfig['home_uri'],
-                                'title' => $aConfig['title']
-                            );
-
-                            $sLinkMarket = $oTemplate->parseHtmlByContent(bx_html_attribute($aConfig['product_url']), $aTmplVars, array('{', '}'));
-                        }
-
-                        $aProducts[$sTitle] = array(
-                        	'name' => $aConfig['name'],
-                            'title' => $sTitle,
-                        	'vendor' => $aConfig['vendor'],
-                        	'version' => $aConfig['version'],
-                            'uri' => $aConfig['home_uri'],
-                            'dir' => $aConfig['home_dir'],
-                            'title' => $sTitle,
-                            'note' => isset($aConfig['note']) ? bx_process_output($aConfig['note']) : '',
-                            'link_market' => $sLinkMarket,
-                            'installed' => $bInstalled,
-                            'enabled' => $bInstalled && $bEnabled
-                        );
-                    }
-                    closedir($rHandleModule);
-                }
-            }
-            closedir($rHandleVendor);
+    protected function loadFeatured($iStart, $iPerPage) {
+    	bx_import('BxDolStudioJson');
+        return BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_featured', array('start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient));
+    }
+
+	protected function loadTag($sTag, $iStart, $iPerPage) {
+    	bx_import('BxDolStudioJson');
+        return BxDolStudioJson::getInstance()->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_by_tag', array('value' => $this->aliasToNameTag($sTag), 'start' => $iStart, 'per_page' => $iPerPage, 'client' => $this->iClient));
+    }
+
+	protected function loadPurchases() {
+    	bx_import('BxDolStudioOAuth');
+        return BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'purchased_products'));
+    }
+
+	protected function loadUpdates() {
+        bx_import('BxDolModuleQuery');
+        $oModules = BxDolModuleQuery::getInstance();
+        $aModules = $oModules->getModules();
+
+        $aParams = array();
+        foreach($aModules as $aModule) {
+        	if(!isset($aModule['name']) || empty($aModule['name']))
+        		continue;
+
+			$aParams[] = array(
+				'name' => $aModule['name'],
+				'version' => $aModule['version'],
+			);
         }
 
-        ksort($aProducts);
-        return $aProducts;
+        bx_import('BxDolStudioOAuth');
+        return BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'available_updates', 'dol_products' => base64_encode(serialize($aParams))));
+    }
+
+    protected function loadCheckout() {
+    	bx_import('BxDolStudioJson');
+    	$oJson = BxDolStudioJson::getInstance();
+
+    	bx_import('BxDolStudioCart');
+		$aVendors = BxDolStudioCart::getInstance()->parseByVendor();
+
+		$aResult = array();
+		foreach($aVendors as $sVendor => $aItems) {
+			$aIds = $aCounts = array();
+			foreach($aItems as $aItem) {
+				$aIds[] = $aItem['item_id'];
+				$aCounts[$aItem['item_id']] = $aItem['item_count'];
+			}
+
+			$aProducts = $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_browse_selected', array('products' => base64_encode(serialize($aIds))));
+			if(!empty($aProducts))
+				$aResult[$sVendor] = array(
+					'ids' => $aIds,
+					'counts' => $aCounts,
+					'products' => $aProducts
+				);
+		}
+
+        return $aResult;
+    }
+
+    protected function loadDownloaded() {
+    	bx_import('BxDolStudioInstallerUtils');
+    	$oInstallerUtils = BxDolStudioInstallerUtils::getInstance();
+
+        return array(
+        	'modules' => $oInstallerUtils->loadModules(),
+        	'updates' => $oInstallerUtils->loadUpdates()
+        );
+    }
+
+    protected function loadProduct($iId) {
+    	bx_import('BxDolStudioJson');
+        $oJson = BxDolStudioJson::getInstance();
+
+        return $oJson->load(BX_DOL_UNITY_URL_MARKET . 'json_get_product_by_id', array('value' => $iId, 'client' => $this->iClient));
+    }
+
+    /*
+     * NOTE. Is needed for download popup selector. Isn't used for now.
+     */
+    protected function loadFiles($iId, $sType) {
+    	bx_import('BxDolStudioOAuth');
+        return BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'product_files', 'dol_product_id' => $iId, 'dol_file_type' => $sType));
+    }
+
+	protected function loadFile($iId) {
+    	bx_import('BxDolStudioOAuth');
+        $aItem = BxDolStudioOAuth::getInstance()->loadItems(array('dol_type' => 'product_file', 'dol_file_id' => $iId));
+		if(empty($aItem) || !is_array($aItem))
+			return $aItem;
+
+		//--- write ZIP archive.
+		$sFilePath = BX_DIRECTORY_PATH_TMP . $aItem['name'];
+		if (!$rHandler = fopen($sFilePath, 'w'))
+			return _t('_adm_str_err_cannot_write');
+
+		if (!fwrite($rHandler, urldecode($aItem['content'])))
+			return _t('_adm_str_err_cannot_write');
+
+    	fclose($rHandler);
+
+    	//--- Unarchive package.
+    	if(!class_exists('ZipArchive'))
+            return _t('_adm_str_err_zip_not_available');
+
+		$oZip = new ZipArchive();
+		if($oZip->open($sFilePath) !== true)
+        	return _t('_adm_str_err_cannot_unzip_package');
+
+        $sPackageRootFolder = $oZip->numFiles > 0 ? $oZip->getNameIndex(0) : false;
+		if($sPackageRootFolder && file_exists(BX_DIRECTORY_PATH_TMP . $sPackageRootFolder)) // remove existing tmp folder with the same name
+        	bx_rrmdir(BX_DIRECTORY_PATH_TMP . $sPackageRootFolder);
+
+		if($sPackageRootFolder && !$oZip->extractTo(BX_DIRECTORY_PATH_TMP))
+        	return _t('_adm_str_err_cannot_unzip_package');
+
+		$oZip->close();
+
+		//--- Move unarchived package.
+		$sLogin = getParam('sys_ftp_login');
+		$sPassword = getParam('sys_ftp_password');
+		$sPath = getParam('sys_ftp_dir');
+		if(empty($sLogin) || empty($sPassword) || empty($sPath))
+			return _t('_adm_str_err_no_ftp_info');
+
+		bx_import('BxDolFtp');
+		$oFtp = new BxDolFtp($_SERVER['HTTP_HOST'], $sLogin, $sPassword, $sPath);
+
+		if(!$oFtp->connect())
+        	return _t('_adm_str_err_cannot_connect_to_ftp');
+
+		if(!$oFtp->isDolphin())
+			return _t('_adm_str_err_destination_not_valid');
+
+		$sConfigPath = BX_DIRECTORY_PATH_TMP . $sPackageRootFolder . '/install/config.php';
+		if(!file_exists($sConfigPath))
+			return _t('_adm_str_err_wrong_package_format');
+
+		include($sConfigPath);
+		if(empty($aConfig) || empty($aConfig['home_dir']) || !$oFtp->copy(BX_DIRECTORY_PATH_TMP . $sPackageRootFolder . '/', 'modules/' . $aConfig['home_dir']))
+			return _t('_adm_str_err_ftp_copy_failed');
+
+        return true;
+    }
+
+    private function checkoutCart($sVendor) {
+    	bx_import('BxDolStudioCart');
+		$oCart = BxDolStudioCart::getInstance();
+
+		$aItems = $oCart->getByVendor($sVendor);
+		if(empty($aItems) || !is_array($aItems))
+			return false;
+
+		$aIds = array();
+		foreach($aItems as $aItem)
+			$aIds[] = $aItem['item_id'];
+
+		$sSid = generateSid();
+		return BX_DOL_UNITY_URL_MARKET . 'purchase/' . $sVendor . '?sid=' . $sSid . '&products=' . base64_encode(implode(',', $aIds));
+    }
+
+    private function aliasToNameTag($sAlias) {
+    	return $this->aliasToName('tag', $sAlias);
+    }
+
+	private function aliasToNameCategory($sAlias) {
+    	return $this->aliasToName('category', $sAlias);
+    }
+
+    private function aliasToName($sType, $sAlias) {
+    	return isset($this->aAlias[$sType][$sAlias]) ? $this->aAlias[$sType][$sAlias] : $sAlias;
     }
 }
 /** @} */

+ 36 - 59
studio/classes/BxDolStudioUpdater.php

@@ -11,7 +11,7 @@ defined('BX_DOL') or die('hack attempt');
 bx_import('BxDolStudioInstaller');
 
 class BxDolStudioUpdater extends BxDolStudioInstaller {
-    function BxDolStudioUpdater($aConfig) {
+    public function BxDolStudioUpdater($aConfig) {
         parent::BxDolStudioInstaller($aConfig);
         $this->_sModulePath = $this->_sBasePath . $aConfig['module_dir'];
 
@@ -31,7 +31,7 @@ class BxDolStudioUpdater extends BxDolStudioInstaller {
         ));
     }
 
-    function update($aParams) {
+    public function update($aParams) {
         $oDb = BxDolDb::getInstance();
 
         $aResult = array(
@@ -76,100 +76,77 @@ class BxDolStudioUpdater extends BxDolStudioInstaller {
             $oDb->query($sQuery);
 
             $aFiles = array();
-            $this->_hash(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['home_dir'], $aFiles);
+            $this->_hash(BX_DIRECTORY_PATH_ROOT . 'modules/' . $this->_aConfig['module_dir'], $aFiles);
             foreach($aFiles as $aFile) {
                 $sQuery = $oDb->prepare("INSERT IGNORE INTO `sys_modules_file_tracks`(`module_id`, `file`, `hash`) VALUES(?, ?, ?)", $aModuleInfo['id'], $aFile['file'], $aFile['hash']);
                 $oDb->query($sQuery);
             }
+
+            //--- Remove update pckage ---//
+            $this->delete();
         }
 
         return $aResult;
     }
 
     //--- Action Methods ---//
-    function actionUpdateFiles($bInstall = true) {
+    public function actionUpdateFiles($bInstall = true) {
         $sPath = $this->_sHomePath . 'source/';
         if(!file_exists($sPath))
-            return BX_DOL_INSTALLER_FAILED;
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
 
+		bx_import('BxDolFtp');
         $oFtp = new BxDolFtp($_SERVER['HTTP_HOST'], getParam('sys_ftp_login'), getParam('sys_ftp_password'), getParam('sys_ftp_dir'));
         if($oFtp->connect() == false)
-            return BX_DOL_INSTALLER_FAILED;
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
 
-        return $oFtp->copy($sPath . '*', 'modules/' . $this->_aConfig['module_dir']) ? BX_DOL_INSTALLER_SUCCESS : BX_DOL_INSTALLER_FAILED;
+        return $oFtp->copy($sPath . '*', 'modules/' . $this->_aConfig['module_dir']) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
     }
 
-    function actionUpdateLanguages($bInstall = true) {
+    public function actionUpdateLanguages($bInstall = true) {
         $oDb = BxDolDb::getInstance();
 
-        $aLanguages = $oDb->getAll("SELECT `ID` AS `id`, `Name` AS `name`, `Title` AS `title` FROM `sys_localization_languages`");
+        bx_import('BxDolStudioLanguagesUtils');
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
+        $aLanguages = $oLanguages->getLanguages();
 
         //--- Process languages' key=>value pears ---//
-        $sModuleConfig = $this->_sHomePath .'install/config.php';
+        $sModuleConfig = $this->_sHomePath . 'install/config.php';
         if(!file_exists($sModuleConfig))
-            return array('code' => BX_DOL_INSTALLER_FAILED, 'content' => '_adm_err_modules_module_config_not_found');
+            return BX_DOL_STUDIO_INSTALLER_FAILED;
 
         include($sModuleConfig);
-        $sQuery = $oDb->prepare("SELECT `ID` FROM `sys_localization_categories` WHERE `Name`=? LIMIT 1", $aConfig['language_category']);
-        $iCategoryId = (int)$oDb->getOne($sQuery);
-
-        foreach($aLanguages as $aLanguage)
-            $this->_updateLanguage($bInstall, $aLanguage, $iCategoryId);
+        $iCategoryId = $oLanguages->getLanguageCategory($aConfig['language_category']);
 
-        //--- Recompile all language files ---//
-        $aResult = array();
-        foreach($aLanguages as $aLanguage) {
-            $bResult = compileLanguage($aLanguage['id']);
+        foreach($aLanguages as $sName => $sTitle)
+            $this->_updateLanguage($bInstall, $sName, $iCategoryId);
 
-            if(!$bResult)
-                $aResult[] = $aLanguage['title'];
-        }
-        return empty($aResult) ? BX_DOL_INSTALLER_SUCCESS : array('code' => BX_DOL_INSTALLER_FAILED, 'content' => $aResult);
+        return $oLanguages->compileLanguage(0, true) ? BX_DOL_STUDIO_INSTALLER_SUCCESS : BX_DOL_STUDIO_INSTALLER_FAILED;
     }
 
-    //--- Protected methods ---//
-
-    function _updateLanguage($bInstall, $aLanguage, $iCategoryId = 0) {
+    protected function _updateLanguage($bInstall, $sLanguage, $iCategoryId = 0) {
         $oDb = BxDolDb::getInstance();
+        $oLanguages = BxDolStudioLanguagesUtils::getInstance();
 
-        $sPath = $this->_sHomePath . 'install/langs/' . $aLanguage['name'] . '.php';
-        if(!file_exists($sPath)) return false;
+        $sPath = $this->_sHomePath . 'install/langs/' . $sLanguage . '.xml';
+        $aLanguageInfo = $oLanguages->readLanguage($sPath, 'update');
+        if(empty($aLanguageInfo))
+        	return false;
 
-        include($sPath);
+		$iLanguage = $oLanguages->getLangId($sLanguage);
 
         //--- Process delete ---//
-        if (isset($aLangContentDelete) && is_array($aLangContentDelete)) {
-            foreach ($aLangContentDelete as $sKey) {
-                $sQuery = $oDb->prepare("DELETE FROM `sys_localization_keys`, `sys_localization_strings` USING `sys_localization_keys`, `sys_localization_strings` WHERE `sys_localization_keys`.`ID`=`sys_localization_strings`.`IDKey` AND `sys_localization_keys`.`Key`=? AND `sys_localization_strings`.`IDLanguage`=?", $sKey, $aLanguage['id']);
-                $oDb->query($sQuery);
-            }
-        }
+        if(isset($aLanguageInfo['strings_del']) && is_array($aLanguageInfo['strings_del']))
+			$oLanguages->deleteLanguageKeys($aLanguageInfo['strings_del']);
 
         //--- Process add ---//
-        if (isset($aLangContentAdd) && is_array($aLangContentAdd)) {
-            foreach ($aLangContentAdd as $sKey => $sValue) {
-                $sQuery = $oDb->prepare("INSERT IGNORE INTO `sys_localization_keys`(`IDCategory`, `Key`) VALUES(?, ?)", $iCategoryId, $sKey);
-                $mixedResult = $oDb->query($sQuery);
-                if($mixedResult === false || $mixedResult <= 0)
-                    continue;
-
-                $iLangKeyId = (int)$oDb->lastId();
-                $sQuery = $oDb->prepare("INSERT INTO `sys_localization_strings`(`IDKey`, `IDLanguage`, `String`) VALUES(?, ?, ?)", $iLangKeyId, $aLanguage['id'], $sValue);
-                $oDb->query($sQuery);
-            }
-        }
+        if(isset($aLanguageInfo['strings_add']) && is_array($aLanguageInfo['strings_add']))
+			$oLanguages->addLanguageKeys($iLanguage, $iCategoryId, $aLanguageInfo['strings_add']);
 
-        //--- Process Update ---//
-        if (isset($aLangContentUpdate) && is_array($aLangContentUpdate)) {
-            foreach ($aLangContentUpdate as $sKey => $sValue) {
-                $sQuery = $oDb->prepare("SELECT `ID` FROM `sys_localization_keys` WHERE `Key`=?", $sKey);
-                $iLangKeyId = (int)$MySQL->getOne($sQuery);
-                if($iLangKeyId == 0)
-                    continue;
-
-                $sQuery = $oDb->prepare("UPDATE `sys_localization_strings` SET `String`=? WHERE `IDKey`=? AND `IDLanguage`=?", $sValue, $iLangKeyId, $aLanguage['id']);
-                $oDb->query($sQuery);
-            }
+        //--- Process update ---//
+        if(isset($aLanguageInfo['strings_upd']) && is_array($aLanguageInfo['strings_upd'])) {
+        	$oLanguages->deleteLanguageKeys($aLanguageInfo['strings_upd']);
+        	$oLanguages->addLanguageKeys($iLanguage, $iCategoryId, $aLanguageInfo['strings_upd']);
         }
 
         return true;

+ 8 - 0
studio/inc/utils.inc.php

@@ -6,4 +6,12 @@
  */
 defined('BX_DOL') or die('hack attempt');
 
+
+/*
+ * Returns unique SID for communication with BoonEx Unity.
+ * @see BxDolStudioStore  -> checkoutCart and BxDolStudioOAuth -> authorize
+ */
+function generateSid() {
+	return md5(BX_DOL_URL_ROOT . '_' . BX_DOL_VERSION . '.' . BX_DOL_BUILD);
+}
 /** @} */

+ 296 - 10
studio/js/store.js

@@ -10,12 +10,243 @@ function BxDolStudioStore(oOptions) {
     this.sObjName = oOptions.sObjName == undefined ? 'oBxDolStudioStore' : oOptions.sObjName;
     this.sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'fade' : oOptions.sAnimationEffect;
     this.iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
+
+    this.sIdPageContent = 'bx-std-pc-content';
+    this.sIdPopupProduct = 'bx-std-str-popup-product';
+    this.sIdPopupFiles = 'bx-std-str-popup-files';
+    this.sIdPopupFile = 'bx-std-str-popup-file';
 }
 
+BxDolStudioStore.prototype.addToCart = function(sVendor, iProduct, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'add-to-cart',
+			str_vendor: sVendor,
+			str_item: iProduct,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+
+			if(parseInt(oData.code) == 0) {
+				var oCounter = $('#bx-std-pmi-checkout .bx-std-pmen-item-counter span');
+				oCounter.html(parseInt(oCounter.html()) + 1);
+
+				if(parseInt(oCounter.html()) > 0)
+					oCounter.parent('.bx-std-pmen-item-counter').show();
+			}
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.deleteFromCart = function(sVendor, iProduct, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'delete-from-cart',
+			str_vendor: sVendor,
+			str_item: iProduct,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			if(oData.message.length > 0)
+				$this.showPopup($this.sIdPopupFile, oData.message, iProduct != 0 ? oButton : null);
+
+			var iCode = parseInt(oData.code);
+			var oCounter = $('#bx-std-pmi-checkout .bx-std-pmen-item-counter span');
+			if(iCode == 0 && iProduct != 0) {
+				var oProduct = $('#bx-std-product-' + iProduct);
+				if(oProduct.siblings('.bx-std-product').length == 0)
+					oProduct.parents('.bx-std-block:first').bx_anim('hide', 'fade', 'fast', function() {
+						$(this).remove();
+					});
+				else
+					oProduct.bx_anim('hide', 'fade', 'fast', function() {
+						$(this).remove();
+					});
+
+				oCounter.html(parseInt(oCounter.html()) - 1);
+			}
+			else if(iCode == 0 && iProduct == 0) {
+				var oBlock = $(oButton).parents('.bx-std-block:first');
+				oCounter.html(parseInt(oCounter.html()) - oBlock.find('.bx-std-product').length);
+
+				oBlock.bx_anim('hide', 'fade', 'fast', function() {
+					$(this).remove();
+				});
+			}
+
+			if(parseInt(oCounter.html()) <= 0)
+				oCounter.parent('.bx-std-pmen-item-counter').hide();
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.deleteAllFromCart = function(sVendor, oButton) {
+	this.deleteFromCart(sVendor, 0, oButton);
+};
+
+BxDolStudioStore.prototype.checkoutCart = function(sVendor, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'checkout-cart',
+			str_vendor: sVendor,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			if(oData.message.length > 0)
+				$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+
+			if(parseInt(oData.code) == 0 && oData.redirect.length > 0)
+				document.location=oData.redirect;
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.getFile = function(iProductId, iFileId, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-file',
+			str_id: iFileId,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+			$this.showPopup($this.sIdPopupFile, oData.message, oButton);
+		},
+		'json'
+	);
+};
+
+/*
+ * NOTE. Is needed for download popup selector. Isn't used for now.
+ */
+BxDolStudioStore.prototype.getFiles = function(iId, sType, oButton) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-files',
+			str_id: iId,
+			str_type: sType,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+			$this.showPopup($this.sIdPopupFiles, oData.message, oButton);
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.info = function(iId, oLink) {
+	var oDate = new Date();
+	var $this = this;
+	bx_loading(this.sIdPageContent, true);
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-product',
+			str_id: iId,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			bx_loading($this.sIdPageContent, false);
+
+			var sId = $this.sIdPopupProduct;
+			if(oData.code == 0 && oData.popup.length > 0) {
+		        $('#' + sId).remove();
+				$(oData.popup).appendTo('body');
+				$('#' + sId).dolPopup({
+					onShow: function() {
+						$this.initScreenshots(oData.screenshots);
+					}
+				});
+			}
+			else
+				$this.showPopup(sId, oData.message, oLink);
+		},
+		'json'
+	);
+};
+
+BxDolStudioStore.prototype.initScreenshots = function(iCount) {
+	var iWidth = 202;
+	var iPadding = 20;
+	var iWidthOuter = iWidth + iPadding;
+	var bBusy = false;
+
+	$(".bx-std-pv-screenshots a[rel=group]").fancybox({
+		transitionIn: 'elastic',
+		transitionOut: 'elastic',
+		speedIn: 600,
+		speedOut: 200
+	});
+	if(iCount <= 4)
+		return;
+
+	$(".bx-std-pvs-left").bind('click', function() {
+		if(bBusy || parseInt($(".bx-std-pvs-cnt").css('left')) >= 0)
+			return;
+
+		bBusy = true;
+		$(".bx-std-pvs-cnt").animate({left: '+=' + iWidthOuter}, 500, function() {
+			bBusy = false;
+		});
+	});
+	$(".bx-std-pvs-right").bind('click', function() {
+		if(bBusy || parseInt($(".bx-std-pvs-cnt").css('left')) <= 460 - $(".bx-std-pvs-cnt").width())
+			return;
+
+		bBusy = true;
+		$(".bx-std-pvs-cnt").animate({left: '-=' + iWidthOuter}, 500, function() {
+			bBusy = false;
+		});
+	});
+
+	$(".bx-std-pv-screenshots").hover(function() {
+		$(".bx-std-pvs-left, .bx-std-pvs-right").bx_anim('show', 'fade', 'fast');
+	}, function() {
+		$(".bx-std-pvs-left, .bx-std-pvs-right").bx_anim('hide', 'fade', 'fast');;
+	});
+};
+
 BxDolStudioStore.prototype.install = function(sValue, oInput) {
 	var $this = this;
 	var onSuccess = function() {
-		$(oInput).hide(0, function() {
+		$(oInput).parent('.bx-std-pc-buttons').hide(0, function() {
 			$(this).siblings(':hidden').show(0);
 		});
 	};
@@ -23,8 +254,21 @@ BxDolStudioStore.prototype.install = function(sValue, oInput) {
 	return this.perform('install', sValue, onSuccess);
 };
 
-BxDolStudioStore.prototype.remove = function(sValue) {
-    return this.perform('delete', sValue);
+BxDolStudioStore.prototype.update = function(sValue, oInput) {
+	var $this = this;
+	var onSuccess = function() {
+		$(oInput).parents('.bx-std-product:first').hide();
+	};
+
+	return this.perform('update', sValue, onSuccess);
+};
+
+BxDolStudioStore.prototype.remove = function(sValue, oInput) {
+	var onSuccess = function() {
+		$(oInput).parents('.bx-std-product:first').hide();
+	};
+
+    return this.perform('delete', sValue, onSuccess);
 };
 
 BxDolStudioStore.prototype.perform = function(sType, sValue, onSuccess) {
@@ -34,6 +278,8 @@ BxDolStudioStore.prototype.perform = function(sType, sValue, onSuccess) {
 	if(!sValue)
         return false;
 
+	bx_loading(this.sIdPageContent, true);
+
     $.post(
     	this.sActionsUrl,
     	{
@@ -42,12 +288,10 @@ BxDolStudioStore.prototype.perform = function(sType, sValue, onSuccess) {
     		_t:oDate.getTime()
     	},
     	function (oData) {
-    		if(oData.message.length > 0) {
-    			var sId = 'bx-std-str-popup-' + sType;
-    	        $('#' + sId).remove();
-    	        $('<div id="' + sId + '" style="display: none;"></div>').prependTo('body').html(oData.message);
-	            $('#' + sId).dolPopup({});
-    		}
+    		bx_loading($this.sIdPageContent, false);
+
+    		if(oData.message.length > 0)
+    			$this.showPopup('bx-std-str-popup-' + sType, oData.message);
 
     		if(oData.code == 0 && typeof onSuccess == 'function')
     			onSuccess();
@@ -79,7 +323,7 @@ BxDolStudioStore.prototype.changePage = function(sType) {
 			$('#bx-std-pc-menu > .bx-std-pmi-active').removeClass('bx-std-pmi-active');
 			$('#bx-std-pmi-' + sType).addClass('bx-std-pmi-active');
 
-			$('#bx-std-pc-content').bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
+			$('#' + $this.sIdPageContent).bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
 				$(this).html(oData.content).bx_anim('show', $this.sAnimationEffect, $this.iAnimationSpeed);
 			});
 		},
@@ -88,4 +332,46 @@ BxDolStudioStore.prototype.changePage = function(sType) {
 
 	return true;
 };
+
+BxDolStudioStore.prototype.changePagePaginate = function(oButton, sType, iStart, iPerPage) {
+	var oDate = new Date();
+	var $this = this;
+
+	$.get(
+		this.sActionsUrl,
+		{
+			str_action: 'get-products-by-page',
+			str_type: sType,
+			str_start: iStart,
+			str_per_page: iPerPage,
+			_t:oDate.getTime()
+		},
+		function(oData) {
+			if(oData.code != 0) {
+				alert(oData.message);
+				return;
+			}
+
+			$(oButton).parents('.bx-std-block-content:first').bx_anim('hide', $this.sAnimationEffect, $this.iAnimationSpeed, function() {
+				$(this).html(oData.content).bx_anim('show', $this.sAnimationEffect, $this.iAnimationSpeed);
+			});
+		},
+		'json'
+	);
+
+	return true;
+};
+
+BxDolStudioStore.prototype.showPopup = function(sId, sContent, mixedPointer) {
+    $('#' + sId).remove();
+    $('<div id="' + sId + '" style="display: none;"></div>').appendTo('body').html(sContent);
+
+    var oParams = {};
+    if(mixedPointer)
+    	oParams.pointer = {
+    		el:$(mixedPointer)
+    	};
+
+    $('#' + sId).dolPopup(oParams);
+};
 /** @} */

+ 1 - 0
studio/template/block_caption.html

@@ -3,6 +3,7 @@
     <bx_if:show_actions>
         <div class="bx-std-bcap-actions">
             <bx_repeat:actions>
+            	<div class="bx-std-bcap-action-div bx-def-padding-sec-leftright">&bull;</div>
                 <div id="bx-std-bca-__name__" class="bx-std-bcap-action">
                     <a href="__url__" title="__title__" <bx_if:show_onclick>onclick="__onclick__"</bx_if:show_onclick>>
                         __caption__

+ 24 - 1
studio/template/css/default.css

@@ -26,6 +26,12 @@
 .bx-def-margin-top {
     margin-top:20px;
 }
+.bx-def-margin-top-auto {
+    margin-top:20px;
+}
+.bx-def-margin-top-auto:first-child {
+    margin-top:0px;
+}
 .bx-def-margin-right {
     margin-right:20px;
 }
@@ -35,7 +41,12 @@
 .bx-def-margin-left {
     margin-left:20px;
 }
-
+.bx-def-margin-left-auto {
+    margin-left:20px;
+}
+.bx-def-margin-left-auto:first-child {
+    margin-left:0px;
+}
 .bx-def-margin-sec-center {
     margin:10px auto;
 }
@@ -55,6 +66,12 @@
 .bx-def-margin-sec-top {
     margin-top:10px;
 }
+.bx-def-margin-sec-top-auto {
+    margin-top:10px;
+}
+.bx-def-margin-sec-top-auto:first-child {
+    margin-top:0px;
+}
 .bx-def-margin-sec-right {
     margin-right:10px;
 }
@@ -64,6 +81,12 @@
 .bx-def-margin-sec-left {
     margin-left:10px;
 }
+.bx-def-margin-sec-left-auto {
+    margin-left:10px;
+}
+.bx-def-margin-sec-left-auto:first-child {
+    margin-left:0px;
+}
 
 /* paddings */
 .bx-def-padding-non {

+ 9 - 1
studio/template/css/general.css

@@ -141,6 +141,8 @@ div.bx-std-pmen-item {
 div.bx-std-pmen-item-cnt {
 	padding-left: 66px;
 }
+span.bx-std-pmen-item-counter {}
+span.bx-std-pmen-ic-num {}
 div.bx-std-pmen-item img {
 	position: absolute;
 	top: 7px;
@@ -183,9 +185,15 @@ div.bx-std-bcap-actions {
     top: 0px;
     right: 0px;
 }
-div.bx-std-bcap-action {
+div.bx-std-bcap-action,
+div.bx-std-bcap-action-div {
+	position: relative;
+	float: left;
     line-height: 25px;
 }
+div.bx-std-bcap-action-div:first-child {
+	display: none;
+}
 div.bx-std-block-panel-top {}
 div.bx-std-block-content {}
 div.bx-std-block-panel-bottom {

+ 104 - 4
studio/template/css/store.css

@@ -1,8 +1,16 @@
-/*--- Product's style ---*/
+/*--- Product's Snippet style ---*/
+div.bx-std-products {
+	position: relative;
+}
+
+div.bx-std-products div.bx-paginate div.bx-paginate-btns {
+	margin-left: 0px;
+}
+
 div.bx-std-product {
 	position: relative;
     float: left;
-	width: 230px;
+	width: 250px;
 	height: 64px;
 	margin: 0px 0px 20px 0px;
 }
@@ -20,12 +28,104 @@ div.bx-stdp-icon img {
 div.bx-stdp-content {
 	min-height: 64px;
     margin-left: 74px; 
+	margin-right: 10px;
+	overflow: hidden;
 }
 div.bx-std-pc-title {
 	padding: 5px 0px 0px 0px;
+	white-space: nowrap;	
 }
-div.bx-std-pc-vendor {
+div.bx-std-pc-vendor,
+div.bx-std-pc-count {
 	padding: 0px 0px 3px 0px;
+	white-space: nowrap;
 	font-size: 11px;
 }
-div.bx-std-pc-action {}
+span.bx-std-pc-price,
+span.bx-std-pc-version,
+span.bx-std-pc-versions {
+	font-weight: bold;
+}
+div.bx-std-pc-action {}
+
+/*--- Product's Files style ---*/
+div.bx-std-file .bx-btn {
+	float: none;
+}
+table.bx-std-files .bx-std-ftitle {
+	padding-right: 20px; 
+	text-align: left;
+}
+table.bx-std-files .bx-std-fversion {
+	padding-right: 20px;
+	text-align: left;
+}
+
+/*--- Product View style ---*/
+div.bx-std-pv-line {
+	margin-top: 5px;
+}
+div.bx-std-pv-actions {}
+div.bx-std-pv-line span.bx-std-pv-cpt {
+	font-weight: bold;
+}
+
+div.bx-std-pv-screenshots {
+	position: relative;
+	height: 208px;
+	overflow: hidden;
+}
+div.bx-std-pvs-cnt {
+	position: relative;
+	padding: 3px;
+}
+div.bx-std-pvs-left,
+div.bx-std-pvs-right {
+	position: absolute;
+	z-index: 3;
+	display: none;
+	width: 50px;
+	height: 50px;
+	top: 76px;
+	background-color: rgba(255, 255, 255, 0.7);
+	border-radius:15px;
+	-moz-border-radius:15px;
+	-webkit-border-radius:15px;
+	line-height: 0.92em;
+	font-size: 70px;
+	cursor: pointer; 
+}
+div.bx-std-pvs-left {
+	left: 15px;
+	text-align: left;
+}
+div.bx-std-pvs-left i.sys-icon {
+	margin-left: 10px;
+	opacity: 0.5;
+}
+div.bx-std-pvs-right {
+	right: 15px;
+	text-align: right;
+}
+div.bx-std-pvs-right i.sys-icon {
+	margin-right: 10px;
+	opacity: 0.5;
+}
+div.bx-std-pvs-item {
+	position: relative;	
+	float: left;
+	width: 202px;
+    height: 202px;
+	line-height: 240px;
+	
+	background-position: center center;
+	background-repeat: no-repeat;
+	background-color: #ffffff;
+
+	-webkit-box-shadow: 0 0 3px rgba(51, 51, 51, 0.3);
+    -moz-box-shadow: 0 0 3px rgba(51, 51, 51, 0.3);
+    box-shadow: 0 0 3px rgba(51, 51, 51, 0.3);
+
+	text-align: center;
+	overflow: hidden;
+}

BIN
studio/template/images/icons/mi-str-downloaded.png


+ 1 - 0
studio/template/menu_side_counter.html

@@ -0,0 +1 @@
+<span class="bx-std-pmen-item-counter" style="<bx_if:hide_counter>display:none;</bx_if:hide_counter>">(<span class="bx-std-pmen-ic-num">__counter__</span>)</span>

+ 0 - 17
studio/template/product.html

@@ -1,17 +0,0 @@
-<div class="bx-std-product">
-    <div class="bx-stdp-icon">
-        <a href="__url__">
-            <img src="__icon__" />
-        </a>
-    </div>
-    <div class="bx-stdp-content">
-        <div class="bx-std-pc-title">
-            <a href="__url__">__title__</a>
-        </div>
-        <div class="bx-std-pc-vendor bx-def-color-ft-gray-l3">__vendor__</div>
-        <div class="bx-std-pc-action">
-            <input class="bx-btn bx-btn-small" <bx_if:hide_install>style="display:none"</bx_if:hide_install> type="button" value="<bx_text:_adm_btn_install_submit />" onclick="javascript:__js_object__.install('__dir__', this)" />
-            <input class="bx-btn bx-btn-small bx-btn-disabled" <bx_if:hide_installed>style="display:none"</bx_if:hide_installed> type="button" value="<bx_text:_adm_btn_installed_submit />" />
-        </div>
-    </div>
-</div>

+ 4 - 1
studio/template/scripts/BxBaseStudioPage.php

@@ -122,9 +122,12 @@ class BxBaseStudioPage extends BxDolStudioPage {
         return '';
     }
 
-    function getPageMenu($aMenu) {
+    function getPageMenu($aMenu, $aMarkers = array()) {
         bx_import('BxTemplStudioMenu');
         $oMenu = new BxTemplStudioMenu(array('template' => 'menu_side.html', 'menu_items' => $aMenu));
+        if(!empty($aMarkers))
+        	$oMenu->addMarkers($aMarkers);
+
         return $oMenu->getCode();
     }
 

+ 478 - 23
studio/template/scripts/BxBaseStudioStore.php

@@ -13,6 +13,8 @@ bx_import('BxDolStudioStore');
 bx_import('BxTemplStudioFunctions');
 
 class BxBaseStudioStore extends BxDolStudioStore {
+	protected $iPerPageDefault = 4;
+
     function BxBaseStudioStore($sPage = "") {
         parent::BxDolStudioStore($sPage);
     }
@@ -22,7 +24,7 @@ class BxBaseStudioStore extends BxDolStudioStore {
     }
 
     function getPageJs() {
-        return array_merge(parent::getPageJs(), array('store.js'));
+        return array_merge(parent::getPageJs(), array('jquery.migrate.js', 'jquery.fancybox.js', 'store.js'));
     }
 
     function getPageJsObject() {
@@ -31,9 +33,10 @@ class BxBaseStudioStore extends BxDolStudioStore {
 
     function getPageMenu() {
         $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
 
         $aMenu = array();
-        $aMenuItems = array('goodies', 'featured', 'purchases', 'updates', 'checkout');
+        $aMenuItems = array('goodies', 'featured', 'purchases', 'updates', 'checkout', 'downloaded');
         foreach($aMenuItems as $sMenuItem)
             $aMenu[] = array(
                 'name' => $sMenuItem,
@@ -43,7 +46,20 @@ class BxBaseStudioStore extends BxDolStudioStore {
             	'selected' => $sMenuItem == $this->sPage
             );
 
-        return parent::getPageMenu($aMenu);
+		bx_import('BxDolStudioCart');
+		$iCounter = BxDolStudioCart::getInstance()->getCount();
+
+		$aMarkers = array(
+			'checkout_counter' => $oTemplate->parseHtmlByName('menu_side_counter.html', array(
+				'bx_if:hide_counter' => array(
+					'condition' => $iCounter <= 0,
+					'content' => array()
+				),
+				'counter' => $iCounter
+			))
+		);
+
+        return parent::getPageMenu($aMenu, $aMarkers);
     }
 
     function getPageCode() {
@@ -54,30 +70,124 @@ class BxBaseStudioStore extends BxDolStudioStore {
         return $this->$sMethod();
     }
 
+	function getPageContent() {
+        $sMethod = 'get' . ucfirst($this->sPage);
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        return $this->$sMethod(false);
+    }
+
     protected function getGoodies() {
         $sJsObject = $this->getPageJsObject();
         $oTemplate = BxDolStudioTemplate::getInstance();
 
         $aProducts = $this->loadGoodies();
 
-        $TmplVars = array(
+        $aTmplVars = array(
 			'js_object' => $sJsObject,
             'bx_repeat:blocks' => array()
         );
 
         $sActions = "";
         foreach($aProducts as $aBlock) {
-            $sItems = "";
-            foreach($aBlock['items'] as $aProduct)
-                $sItems .= $oTemplate->parseHtmlByName('product.html', array());
+            $sItems = $this->displayProducts($aBlock['items']);
 
-            $TmplVars['bx_repeat:blocks'][] = array(
+            $aTmplVars['bx_repeat:blocks'][] = array(
             	'caption' => $this->getBlockCaption($aBlock),
                 'items' => $sItems
             );
         }
 
-        return $oTemplate->parseHtmlByName('store.html', $TmplVars);
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+	protected function getFeatured($bWrapInBlock = true) {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iStart = (int)bx_get('str_start');
+        $iPerPage = (int)bx_get('str_per_page');
+        if(empty($iPerPage))
+        	$iPerPage = $this->iPerPageDefault; 
+
+        $aProducts = $this->loadFeatured($iStart, $iPerPage + 1);
+
+        bx_import('BxTemplPaginate');
+        $oPaginate = new BxTemplPaginate(array(
+			'start' => $iStart,
+			'per_page' => $iPerPage,
+        	'on_change_page' => $sJsObject . ".changePagePaginate(this, 'featured', {start}, {per_page})"
+        ));
+        $oPaginate->setNumFromDataArray($aProducts);
+
+        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+        	'list' => $this->displayProducts($aProducts),
+        	'paginate' => $oPaginate->getSimplePaginate()
+        ));
+
+        if(!$bWrapInBlock)
+        	return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_featured'), 'actions' => array())),
+                    'items' => $sContent
+                )
+            )
+        ));
+    }
+
+    protected function getModules($bWrapInBlock = true) {
+    	return $this->getTag('modules', $bWrapInBlock);
+    }
+
+	protected function getTemplates($bWrapInBlock = true) {
+    	return $this->getTag('templates', $bWrapInBlock);
+    }
+
+	protected function getLanguages($bWrapInBlock = true) {
+    	return $this->getTag('languages', $bWrapInBlock);
+    }
+
+	protected function getTag($sTag, $bWrapInBlock = true) {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $iStart = (int)bx_get('str_start');
+        $iPerPage = (int)bx_get('str_per_page');
+        if(empty($iPerPage))
+        	$iPerPage = $this->iPerPageDefault; 
+
+        $aProducts = $this->loadTag($sTag, $iStart, $iPerPage + 1);
+
+        bx_import('BxTemplPaginate');
+        $oPaginate = new BxTemplPaginate(array(
+			'start' => $iStart,
+			'per_page' => $iPerPage,
+        	'on_change_page' => $sJsObject . ".changePagePaginate(this, '" . $sTag . "', {start}, {per_page})"
+        ));
+        $oPaginate->setNumFromDataArray($aProducts);
+
+        $sContent = $oTemplate->parseHtmlByName('str_products.html', array(
+        	'list' => $this->displayProducts($aProducts),
+        	'paginate' => $oPaginate->getSimplePaginate()
+        ));
+
+        if(!$bWrapInBlock)
+        	return $sContent;
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_' . $sTag), 'actions' => array())),
+                    'items' => $sContent
+                )
+            )
+        ));
     }
 
     protected function getPurchases() {
@@ -85,42 +195,387 @@ class BxBaseStudioStore extends BxDolStudioStore {
         $oTemplate = BxDolStudioTemplate::getInstance();
 
         $aProducts = $this->loadPurchases();
+        
+        $aTmplVars = array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_purchases'), 'actions' => array())),
+                    'items' => $this->displayProducts($aProducts)
+                )
+            )
+        );
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+	protected function getUpdates() {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aUpdates = $this->loadUpdates();
+        
+        $aTmplVars = array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_updates'), 'actions' => array())),
+                    'items' => $this->displayUpdates($aUpdates)
+                )
+            )
+        );
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+	protected function getCheckout() {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
 
-        $sItems = "";
-        foreach($aProducts as $aProduct) {
-            $sIcon = $oTemplate->getIconUrl($aProduct['name'] . '@modules/' . $aProduct['dir'] . '|std-si.png');
+        //--- Empty cart by vendor if the payment was accepted.
+        $mixedVendor = bx_get('vendor');
+        $mixedProducts = bx_get('products');
+        if($mixedVendor !== false && $mixedProducts !== false) {
+        	bx_import('BxDolStudioCart');
+			$oCart = BxDolStudioCart::getInstance();
+
+			$aProducts = explode(',', base64_decode($mixedProducts));
+			foreach($aProducts as $iId) 
+				$oCart->delete($mixedVendor, $iId);
+
+        	return $this->getMessage('_adm_block_cpt_checkout', '_adm_msg_modules_success_checkouted');
+        }
+
+        $aVendors = $this->loadCheckout();
+
+        $aTmplVarsBlocks = array();
+        foreach($aVendors as $sName => $aInfo) {
+        	$fTotal = 0;
+        	$sCurrency = '';
+        	foreach($aInfo['products'] as $aProduct) {
+        		$iCount = isset($aInfo['counts'][$aProduct['id']]) ? (int)$aInfo['counts'][$aProduct['id']] : 1;
+        		$fTotal += $iCount * $aProduct['price'];
+
+        		if($sCurrency == '' && isset($aProduct['currency_sign']))
+        			$sCurrency = $aProduct['currency_sign'];
+        	}
+
+        	$aActions = array(
+        		array('name' => 'checkout-' . $sName, 'caption' => '_adm_action_cpt_checkout', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".checkoutCart('" . $sName . "', this);"),
+        		array('name' => 'delete-all-' . $sName, 'caption' => '_adm_action_cpt_delete_all', 'url' => 'javascript:void(0)', 'onclick' => $sJsObject . ".deleteAllFromCart('" . $sName . "', this)")
+        	);
+
+        	$aTmplVarsBlocks[] = array(
+				'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_checkout_by_vendor_csign', $sName, $sCurrency, $fTotal), 'actions' => $aActions)),
+                'items' => $this->displayProducts($aInfo['products'], array('is_shopping_cart' => true, 'counts' => $aInfo['counts']))
+			);
+        }
+
+        if(empty($aTmplVarsBlocks)) 
+        	return $this->getMessage('_adm_block_cpt_checkout', '_Empty');
+
+        return $oTemplate->parseHtmlByName('store.html', array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => $aTmplVarsBlocks
+        ));
+    }
+
+	protected function getDownloaded() {
+        $sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $sModules = $sUpdates = "";
+        $aProducts = $this->loadDownloaded();
+
+		//--- Prepare modules.
+        foreach($aProducts['modules'] as $aModule) {
+            $sIcon = $oTemplate->getIconUrl($aModule['name'] . '@modules/' . $aModule['dir'] . '|std-si.png');
             if(empty($sIcon))
                 $sIcon = $oTemplate->getIconUrl('pi-str-dump.png');
 
-            $sItems .= $oTemplate->parseHtmlByName('product.html', array(
+            $sModules .= $oTemplate->parseHtmlByName('str_product_v1.html', array(
             	'js_object' => $sJsObject,
-                'url' => $aProduct['link_market'],
+                'url' => $aModule['link_market'],
                 'icon' => $sIcon,
-                'title' => $aProduct['title'],
-                'vendor' => $aProduct['vendor'],
-            	'dir' => $aProduct['dir'],
+                'title' => $aModule['title'],
+            	'vendor' => $aModule['vendor'],
+                'version' => $aModule['version'],
+            	'dir' => $aModule['dir'],
                 'bx_if:hide_install' => array(
-                    'condition' => $aProduct['installed'],
+                    'condition' => $aModule['installed'],
                     'content' => array()
                 ),
                 'bx_if:hide_installed' => array(
-                    'condition' => !$aProduct['installed'],
+                    'condition' => !$aModule['installed'],
                     'content' => array()
                 )
             ));
         }
 
-        $TmplVars = array(
+		//--- Prepare updates.
+        foreach($aProducts['updates'] as $aUpdate) {
+            $sIcon = $oTemplate->getIconUrl($aUpdate['module_name'] . '@modules/' . $aUpdate['module_dir'] . '|std-si.png');
+            if(empty($sIcon))
+                $sIcon = $oTemplate->getIconUrl('pi-str-dump.png');
+
+            $sUpdates .= $oTemplate->parseHtmlByName('str_update_v1.html', array(
+            	'js_object' => $sJsObject,
+                'url' => $aUpdate['module_link_market'],
+                'icon' => $sIcon,
+                'title' => $aUpdate['title'],
+                'vendor' => $aUpdate['vendor'],
+            	'versions' => _t('_adm_str_txt_update_from_to', $aUpdate['version_from'], $aUpdate['version_to']),
+            	'dir' => $aUpdate['dir']
+            ));
+        }
+
+        $aTmplVars = array(
 			'js_object' => $sJsObject,
             'bx_repeat:blocks' => array(
                 array(
-                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_purchases'), 'actions' => array())),
-                    'items' => $sItems
+                	'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_downloaded_modules'), 'actions' => array())),
+                    'items' => $sModules
                 )
             )
         );
 
-        return $oTemplate->parseHtmlByName('store.html', $TmplVars);
+        if(!empty($sUpdates))
+        	$aTmplVars['bx_repeat:blocks'][] = array(
+				'caption' => $this->getBlockCaption(array('caption' => _t('_adm_block_cpt_downloaded_updates'), 'actions' => array())),
+				'items' => $sUpdates
+			);
+
+        return $oTemplate->parseHtmlByName('store.html', $aTmplVars);
+    }
+
+    protected function getMessage($sCaption, $sContent, $aActions = array()) {
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+    	return $oTemplate->parseHtmlByName('store.html', array(
+			'js_object' => $sJsObject,
+            'bx_repeat:blocks' => array(
+                array(
+                	'caption' => $this->getBlockCaption(array('caption' => _t($sCaption), 'actions' => $aActions)),
+                    'items' => MsgBox(_t($sContent))
+                )
+            )
+		));
+    }
+
+    protected function getProduct($iId) {
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aProduct = $this->loadProduct($iId);
+        if(!is_array($aProduct))
+        	return array('code' => 1, 'message' => (!empty($aProduct) ? $aProduct : _t('_adm_str_err_no_product_info')));
+
+		$bFree = (int)$aProduct['is_free'] == 1;
+		$bPurchased = (int)$aProduct['is_purchased'] == 1;
+		$bDownloadable = (int)$aProduct['is_file'] == 1;
+		$bDiscount = !empty($aProduct['discount']);
+		$bScreenshots = is_array($aProduct['screenshots']) && !empty($aProduct['screenshots']);
+
+		$iScreenshots = 0;
+		$aScreenshots = array();
+		if($bScreenshots) {
+			$iScreenshots = count($aProduct['screenshots']);
+			foreach($aProduct['screenshots'] as $aScreenshot) 
+				$aScreenshots[] = array(
+					'view_url' => $aProduct['url'],
+					'image_url' => $aScreenshot['big']
+				);
+		}
+
+        $sContent = $oTemplate->parseHtmlByname('str_product_view.html', array(
+        	'id' => $aProduct['id'],
+        	'title' => $aProduct['title'],
+        	'url' => $aProduct['url'],
+        	'author_name' => $aProduct['author_name'],
+        	'author_url' => $aProduct['author_url'],
+        	'version' => $aProduct['version'],
+	        'price' => !$bFree ? _t('_adm_str_txt_price_csign', $aProduct['author_currency_sign'], $aProduct['price']) : _t('_adm_str_txt_price_free'),
+        	'bx_if:show_discount' => array(
+        		'condition' => !$bFree && $bDiscount,
+        		'content' => array(
+        			'discount' => $bDiscount ? _t('_adm_str_txt_pv_discount_off_csign', $aProduct['discount']['percent'], $aProduct['author_currency_sign'], $aProduct['discount']['save']) : ''
+        		)
+        	),
+        	'category' => $aProduct['category'],
+        	'category_url' => $aProduct['category_url'],
+        	'tags' => implode(', ', explode(',', $aProduct['tags'])),
+	        'reviews' => _t('_adm_str_txt_pv_stats_reviews', $aProduct['reviews_cnt']),
+        	'reviews_url' => $aProduct['reviews_url'],
+        	'likes' => _t('_adm_str_txt_pv_stats_likes', $aProduct['likes_cnt']),
+			'views' => _t('_adm_str_txt_pv_stats_views', $aProduct['views_cnt']),
+	        'created' => $aProduct['created'],
+        	'updated' => $aProduct['updated'],
+        	'description' => $aProduct['description'],
+        	'bx_if:show_screenshots' => array(
+        		'condition' => $bScreenshots,
+        		'content' => array(
+        			'width' => 222 * $iScreenshots - 20,
+        			'bx_repeat:screenshots' => $aScreenshots
+        		)
+        	),
+        	'bx_if:show_purchase' => array(
+        		'condition' => !$bFree && !$bPurchased,
+        		'content' => array(
+        			'js_object' => $sJsObject,
+        			'id' => $aProduct['id'],
+        			'vendor' => $aProduct['author_name'],
+        		)
+        	),
+        	'bx_if:show_download' => array(
+        		'condition' => ($bFree || $bPurchased) && $bDownloadable,
+        		'content' => array(
+        			'js_object' => $sJsObject,
+        			'id' => $aProduct['id'],
+        			'file_id' => $aProduct['file_id'],
+        		)
+        	)
+        ));
+
+        return array('code' => 0, 'message' => '', 'popup' => PopupBox('bx-std-str-popup-product', $aProduct['title'], $sContent, true), 'screenshots' => $iScreenshots);
+    }
+
+    /*
+     * NOTE. Is needed for download popup selector. Isn't used for now.
+     */
+    protected function getFiles($iId, $sType) {
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $aFiles = $this->loadFiles($iId, $sType);
+        if(!is_array($aFiles))
+        	return array('code' => 1, 'message' => (!empty($aFiles) ? $aFiles : _t('_adm_str_err_no_files')));
+        
+        $aTmplVarsFiles = array();
+        foreach($aFiles as $aFile)
+        	$aTmplVarsFiles[] = array(
+        		'js_object' => $sJsObject,
+        		'product_id' => $iId,
+        		'id' => $aFile['id'],
+        		'title' => $aFile['name'],
+        		'version' => empty($aFile['version_to']) ? $aFile['version'] : _t('_adm_str_txt_update_from_to', $aFile['version'], $aFile['version_to']), 
+        	);
+
+		$sContent = $oTemplate->parseHtmlByName('str_files.html', array(
+        	'bx_repeat:files' => $aTmplVarsFiles
+        ));
+
+        return array('code' => 0, 'message' => $sContent);
+    }
+
+    protected function getFile($iId) {
+    	$mixedResult = $this->loadFile($iId);
+        if($mixedResult !== true)
+        	return array('code' => 1, 'message' => (!empty($mixedResult) ? $mixedResult : _t('_adm_str_err_download_failed')));
+
+		return array('code' => 0, 'message' => _t('_adm_str_msg_download_successfully'));
+    }
+
+    protected function displayProducts($aItems, $aParams = array()) {
+    	if(!is_array($aItems)) 
+    		return $aItems;
+
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+        $bShoppingCart = isset($aParams['is_shopping_cart']) && $aParams['is_shopping_cart'];
+
+		$sResult = '';
+		foreach($aItems as $aItem) {
+			$bFree = (int)$aItem['is_free'] == 1;
+			$bPurchased = (int)$aItem['is_purchased'] == 1;
+			$bDownloadable = (int)$aItem['is_file'] == 1;
+			$sPrice = !$bFree ? _t('_adm_str_txt_price_csign', $aItem['currency_sign'], $aItem['price']) : _t('_adm_str_txt_price_free');
+			$sDiscount = !$bFree && !empty($aItem['discount']) ? _t('_adm_str_txt_discount_csign', $aItem['currency_sign'], $aItem['discount']['price']) : '';
+
+        	$sResult .= $oTemplate->parseHtmlByName('str_product_v2.html', array(
+        		'js_object' => $sJsObject,
+        		'id' => $aItem['id'],
+	            'url' => $aItem['url'],
+	            'icon' => !empty($aItem['thumbnail']['medium']) ? $aItem['thumbnail']['medium'] : $oTemplate->getIconUrl('pi-str-dump.png'),
+	            'title' => $aItem['title'],
+        		'bx_if:show_vendor_price' => array(
+        			'condition' => !$bShoppingCart,
+        			'content' => array(
+        				'vendor' => $aItem['author'],
+        				'price' => $sPrice,
+        				'discount' => $sDiscount,
+        			)
+        		),
+        		'bx_if:show_count_price' => array(
+        			'condition' => $bShoppingCart,
+        			'content' => array(
+        				'count' => isset($aParams['counts'][$aItem['id']]) ? (int)$aParams['counts'][$aItem['id']] : 1,
+        				'price' => $sPrice,
+        				'discount' => $sDiscount,
+        			)
+        		),
+        		'bx_if:show_purchase' => array(
+        			'condition' => !$bShoppingCart && !$bFree && !$bPurchased, 
+        			'content' => array(
+        				'js_object' => $sJsObject,
+        				'id' => $aItem['id'],
+        				'vendor' => $aItem['author']
+        			)
+        		),
+        		'bx_if:show_download' => array(
+        			'condition' => !$bShoppingCart && ($bFree || $bPurchased) && $bDownloadable,
+        			'content' => array(
+        				'js_object' => $sJsObject,
+        				'id' => $aItem['id'],
+        				'file_id' => $aItem['file_id'],
+        			)
+        		),
+        		'bx_if:show_delete' => array(
+        			'condition' => $bShoppingCart,
+        			'content' => array(
+        				'js_object' => $sJsObject,
+        				'id' => $aItem['id'],
+        				'vendor' => $aItem['author']
+        			)
+        		)
+			));
+		}
+
+        return $sResult;
+    }
+
+	protected function displayUpdates($aItems, $aParams = array()) {
+    	if(!is_array($aItems)) 
+    		return $aItems;
+
+    	$sJsObject = $this->getPageJsObject();
+        $oTemplate = BxDolStudioTemplate::getInstance();
+
+		$sResult = '';
+		foreach($aItems as $aItem) {
+			$bDownloadable = (int)$aItem['is_file'] == 1;
+
+        	$sResult .= $oTemplate->parseHtmlByName('str_update_v2.html', array(
+        		'js_object' => $sJsObject,
+        		'id' => $aItem['id'],
+	            'url' => $aItem['url'],
+	            'icon' => !empty($aItem['thumbnail']['medium']) ? $aItem['thumbnail']['medium'] : $oTemplate->getIconUrl('pi-str-dump.png'),
+	            'title' => $aItem['title'],
+	            'vendor' => $aItem['author'],
+        		'versions' => _t('_adm_str_txt_update_from_to', $aItem['file_version'], $aItem['file_version_to']),
+        		'bx_if:show_download' => array(
+        			'condition' => $bDownloadable,
+        			'content' => array(
+        				'js_object' => $sJsObject,
+        				'id' => $aItem['id'],
+        				'file_id' => $aItem['file_id'],
+        			)
+        		)
+			));
+		}
+
+        return $sResult;
     }
 }
 /** @} */

+ 18 - 0
studio/template/str_files.html

@@ -0,0 +1,18 @@
+<div class="bx-std-files">
+	<table class="bx-std-files">
+		<tr>
+			<th class="bx-std-ftitle"><bx_text:_adm_str_cpt_files_file /></th>
+			<th class="bx-std-fversion"><bx_text:_adm_str_cpt_files_version /></th>
+			<th>&nbsp;</th>
+		</tr>
+		<bx_repeat:files>
+			<tr>
+				<td class="bx-std-ftitle">__title__</td>
+				<td class="bx-std-fversion">__version__</td>
+				<td>
+					<input class="bx-btn bx-btn-small" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__product_id__, __id__, this)" />
+				</td>
+			</tr>
+	    </bx_repeat:files>
+	</table>
+</div>

+ 22 - 0
studio/template/str_product_v1.html

@@ -0,0 +1,22 @@
+<div class="bx-std-product">
+    <div class="bx-stdp-icon">
+        <a href="__url__">
+            <img src="__icon__" />
+        </a>
+    </div>
+    <div class="bx-stdp-content">
+        <div class="bx-std-pc-title">
+            <a href="__url__">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-color-ft-gray-l3">__vendor__ &bull; <span class="bx-std-pc-version">__version__</span></div>
+        <div class="bx-std-pc-action">
+        	<div class="bx-std-pc-buttons" <bx_if:hide_install>style="display:none"</bx_if:hide_install>>
+        		<input class="bx-btn bx-btn-small bx-def-margin-sec-left-auto" type="button" value="<bx_text:_adm_btn_install_submit />" onclick="javascript:__js_object__.install('__dir__', this)" />
+        		<input class="bx-btn bx-btn-small bx-def-margin-sec-left-auto" type="button" value="<bx_text:_adm_btn_delete_submit />" onclick="javascript:__js_object__.remove('__dir__', this)" />
+        	</div>
+        	<div class="bx-std-pc-buttons" <bx_if:hide_installed>style="display:none"</bx_if:hide_installed>>
+            	<input class="bx-btn bx-btn-small bx-btn-disabled" type="button" value="<bx_text:_adm_btn_installed_submit />" />
+            </div>
+        </div>
+    </div>
+</div>

+ 29 - 0
studio/template/str_product_v2.html

@@ -0,0 +1,29 @@
+<div class="bx-std-product" id="bx-std-product-__id__">
+    <div class="bx-stdp-icon">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info(__id__, this)">
+            <img src="__icon__" />
+        </a>
+    </div>
+    <div class="bx-stdp-content">
+        <div class="bx-std-pc-title">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info(__id__, this)">__title__</a>
+        </div>
+        <bx_if:show_vendor_price>
+        	<div class="bx-std-pc-vendor bx-def-color-ft-gray-l3">__vendor__ &bull; <span class="bx-std-pc-price">__price__ __discount__</span></div>
+        </bx_if:show_vendor_price>
+        <bx_if:show_count_price>
+        	<div class="bx-std-pc-count bx-def-color-ft-gray-l3">__count__ <bx_text:_adm_str_txt_items_by /> <span class="bx-std-pc-price">__price__ __discount__</span></div>
+        </bx_if:show_count_price>
+        <div class="bx-std-pc-action">
+        	<bx_if:show_purchase>
+            	<input class="bx-btn bx-btn-small bx-std-pc-purchase" type="button" value="<bx_text:_adm_btn_purchase_submit />" onclick="javascript:__js_object__.addToCart('__vendor__', __id__, this)" />
+            </bx_if:show_purchase>
+        	<bx_if:show_download>
+            	<input class="bx-btn bx-btn-small bx-std-pc-download" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__id__, '__file_id__', this)" />
+            </bx_if:show_download>
+            <bx_if:show_delete>
+            	<input class="bx-btn bx-btn-small bx-std-pc-delete" type="button" value="<bx_text:_adm_btn_delete_submit />" onclick="javascript:__js_object__.deleteFromCart('__vendor__', __id__, this)" />
+            </bx_if:show_delete>
+        </div>
+    </div>
+</div>

+ 89 - 0
studio/template/str_product_view.html

@@ -0,0 +1,89 @@
+<div class="bx-std-product-view">
+	<bx_if:show_screenshots>
+		<div class="bx-std-pv-screenshots">
+			<div class="bx-std-pvs-left">
+				<i class="sys-icon caret-left"></i>
+			</div>
+			<div class="bx-std-pvs-cnt" style="width:__width__px">
+				<bx_repeat:screenshots>
+					<div class="bx-std-pvs-item bx-def-margin-left-auto" style="background-image:url('__image_url__');">
+						<a href="__view_url__" rel="group">
+			        		<img src="<bx_image_url:spacer.gif />" />
+			        	</a>
+			        </div>
+				</bx_repeat:screenshots>
+				<div class="clear">&#160;</div>
+			</div>
+			<div class="bx-std-pvs-right">
+				<i class="sys-icon caret-right"></i>
+			</div>
+		</div>
+	</bx_if:show_screenshots>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_title />:
+		</span> <a href="__url__">__title__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_vendor />:
+		</span> <a href="__author_url__">__author_name__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_version />:
+		</span> __version__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_price />:
+		</span> __price__ 
+	</div>
+	<bx_if:show_discount>
+		<div class="bx-std-pv-line">
+			<span class="bx-std-pv-cpt bx-def-font-grayed">
+				<bx_text:_adm_str_txt_pv_discount />:
+			</span> __discount__
+		</div>
+	</bx_if:show_discount>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_category />:
+		</span> <a href="__category_url__">__category__</a>
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_tags />:
+		</span> __tags__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_stats />:
+		</span> <a href="__reviews_url__">__reviews__</a>, __likes__, __views__ 
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_created />:
+		</span> __created__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_updated />:
+		</span> __updated__
+	</div>
+	<div class="bx-std-pv-line">
+		<span class="bx-std-pv-cpt bx-def-font-grayed">
+			<bx_text:_adm_str_txt_pv_description />:
+		</span> __description__
+	</div>
+	<div class="bx-std-pv-actions bx-def-margin-top">
+		<bx_if:show_purchase>
+			<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_purchase_submit />" onclick="javascript:__js_object__.addToCart('__vendor__', __id__, this)" />
+		</bx_if:show_purchase>
+		<bx_if:show_download>
+			<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__id__, '__file_id__', this)" />
+		</bx_if:show_download>
+		<input class="bx-btn bx-def-margin-right" type="button" value="<bx_text:_adm_btn_cancel_submit />" onclick="javascript:$('.bx-popup-applied:visible').dolPopupHide();" />
+		<div class="bx-std-cb"></div>
+	</div>
+</div>

+ 5 - 0
studio/template/str_products.html

@@ -0,0 +1,5 @@
+<div class="bx-std-products">
+    __list__
+    <div class="bx-clear"></div>
+    __paginate__
+</div>

+ 16 - 0
studio/template/str_update_v1.html

@@ -0,0 +1,16 @@
+<div class="bx-std-product bx-stdp-update">
+    <div class="bx-stdp-icon">
+        <a  href="__url__">
+            <img src="__icon__" />
+        </a>
+    </div>
+    <div class="bx-stdp-content">
+        <div class="bx-std-pc-title">
+            <a href="__url__">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-color-ft-gray-l3">__vendor__ &bull; <span class="bx-std-pc-versions">__versions__</span></div>
+        <div class="bx-std-pc-action">
+        	<input class="bx-btn bx-btn-small" type="button" value="<bx_text:_adm_btn_install_submit />" onclick="javascript:__js_object__.update('__dir__', this)" />
+        </div>
+    </div>
+</div>

+ 18 - 0
studio/template/str_update_v2.html

@@ -0,0 +1,18 @@
+<div class="bx-std-product bx-stdp-update" id="bx-std-product-__id__">
+    <div class="bx-stdp-icon">
+        <a href="javascript:void(0)" onclick="javascript:__js_object__.info(__id__, this)">
+            <img src="__icon__" />
+        </a>
+    </div>
+    <div class="bx-stdp-content">
+        <div class="bx-std-pc-title">
+            <a href="javascript:void(0)" onclick="javascript:__js_object__.info(__id__, this)">__title__</a>
+        </div>
+        <div class="bx-std-pc-vendor bx-def-color-ft-gray-l3">__vendor__ &bull; <span class="bx-std-pc-versions">__versions__</span></div>
+        <div class="bx-std-pc-action">
+        	<bx_if:show_download>
+            	<input class="bx-btn bx-btn-small bx-std-pc-download" type="button" value="<bx_text:_adm_btn_download_submit />" onclick="javascript:__js_object__.getFile(__id__, '__file_id__', this)" />
+            </bx_if:show_download>
+        </div>
+    </div>
+</div>