Преглед изворни кода

Ticket #495: Added Social Engine Migration module

Alleha пре 10 година
родитељ
комит
da29b63e9c
23 измењених фајлова са 2136 додато и 0 уклоњено
  1. 373 0
      modules/boonex/se_migration/classes/BxSEDb.php
  2. 29 0
      modules/boonex/se_migration/classes/BxSEMigAlertsResponse.php
  3. 194 0
      modules/boonex/se_migration/classes/BxSEMigBlogs.php
  4. 89 0
      modules/boonex/se_migration/classes/BxSEMigConfig.php
  5. 72 0
      modules/boonex/se_migration/classes/BxSEMigData.php
  6. 136 0
      modules/boonex/se_migration/classes/BxSEMigDb.php
  7. 135 0
      modules/boonex/se_migration/classes/BxSEMigModule.php
  8. 134 0
      modules/boonex/se_migration/classes/BxSEMigPhotoAlbums.php
  9. 269 0
      modules/boonex/se_migration/classes/BxSEMigProfiles.php
  10. 176 0
      modules/boonex/se_migration/classes/BxSEMigProfilesFields.php
  11. 92 0
      modules/boonex/se_migration/classes/BxSEMigStudioPage.php
  12. 21 0
      modules/boonex/se_migration/classes/BxSEMigTemplate.php
  13. 73 0
      modules/boonex/se_migration/classes/BxSEMigTransfers.php
  14. 70 0
      modules/boonex/se_migration/install/config.php
  15. 20 0
      modules/boonex/se_migration/install/installer.php
  16. 56 0
      modules/boonex/se_migration/install/langs/en.xml
  17. 15 0
      modules/boonex/se_migration/install/sql/disable.sql
  18. 37 0
      modules/boonex/se_migration/install/sql/enable.sql
  19. 33 0
      modules/boonex/se_migration/install/sql/install.sql
  20. 8 0
      modules/boonex/se_migration/install/sql/uninstall.sql
  21. 19 0
      modules/boonex/se_migration/request.php
  22. 16 0
      modules/boonex/se_migration/template/css/main.css
  23. 69 0
      modules/boonex/se_migration/template/images/icons/std-icon.svg

+ 373 - 0
modules/boonex/se_migration/classes/BxSEDb.php

@@ -0,0 +1,373 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/** 
+* BxDolDb Class duplicate for connecting with Social Engine database
+* It allows to create two connections for different databases
+*/	
+class SEDB {
+	static $_rLink;
+    static $_aDbCacheData;
+    static $_sErrorKey = 'bx_db_error';	
+    static $_aErrors = array(
+    	BX_DB_ERR_CONNECT_FAILD => 'Database connect failed',
+    	BX_DB_ERR_QUERY_ERROR => 'Database query error',
+    	BX_DB_ERR_ESCAPE => 'Escape string error'
+    );
+
+	var $_bPdoPersistent;
+	var $_iPdoFetchType;
+	var $_iPdoErrorMode;
+
+	var $_bErrorChecking;
+    var $_aError;
+
+	var $_sHost, $_sPort, $_sSocket, $_sDbname, $_sUser, $_sPassword, $_sCharset, $_sStorageEngine;
+
+    var $_oStatement = null;
+    var $_oDbCacheObject = null;
+	
+	public function __construct($aDbConf = false)
+    {
+
+        $this->_iPdoFetchType = PDO::FETCH_ASSOC;
+        $this->_iPdoErrorMode = BX_DB_MODE_EXCEPTION;
+
+        $this->_bErrorChecking = true;
+        $this->_aError = array();
+
+        $this->_sStorageEngine = 'MYISAM';
+				
+		$this->_sHost = $aDbConf['host'];
+        $this->_sPort = $aDbConf['port'];
+        $this->_sSocket = $aDbConf['sock'];
+        $this->_sDbname = $aDbConf['name'];
+        $this->_sUser = $aDbConf['user'];
+        $this->_sPassword = $aDbConf['pwd'];
+        if(isset($aDbConf['charset']))
+        	$this->_sCharset = $aDbConf['charset'];
+
+       @set_exception_handler(array($this, 'pdoExceptionHandler'));
+    }
+	
+	public function connect()
+    {
+    	if(self::$_rLink)
+    		return true;
+
+    	try {
+	    	$sDsn = "mysql:host=" . $this->_sHost . ";";
+	   		$sDsn .= $this->_sPort ? "port=" . $this->_sPort . ";" : "";
+	   		$sDsn .= $this->_sSocket ? "unix_socket=" . $this->_sSocket . ";" : "";
+	    	$sDsn .= "dbname=" . $this->_sDbname . ";charset=" . $this->_sCharset;
+
+	        self::$_rLink = new PDO($sDsn, $this->_sUser, $this->_sPassword, array(
+				PDO::ATTR_ERRMODE => $this->_iPdoErrorMode,
+				PDO::ATTR_DEFAULT_FETCH_MODE => $this->_iPdoFetchType,
+				PDO::ATTR_PERSISTENT => $this->_bPdoPersistent
+	        ));
+
+	    	$this->pdoExec("SET NAMES 'utf8'");
+	        $this->pdoExec("SET sql_mode = ''");
+			$this->pdoExec("SET storage_engine=" . $this->_sStorageEngine);
+
+			self::$_aDbCacheData = array();
+    	}
+    	catch (PDOException $oException) {
+    		$oException->errorInfo[self::$_sErrorKey] = array(
+    			'code' => BX_DB_ERR_CONNECT_FAILD,
+    			'message' => $oException->getMessage(),
+    			'trace' => $oException->getTrace()
+    		);
+
+    		throw $oException;
+    	}
+		
+		return true;
+    }
+	
+	public function res($oStatement, $aBindings = array(), $bVerbose = null)
+    {
+		if(!$oStatement || !($oStatement instanceof PDOStatement))
+            return false;
+
+		if($oStatement->errorCode() == BX_PDO_STATE_SUCCESS)
+			return true;
+
+		$bResult = $this->executeStatement($oStatement, $aBindings, $bVerbose);
+
+		//if mysql connection is lost - reconnect and try again
+        if(!$bResult && !$this->ping()) {
+            $this->disconnect();
+            $this->connect();
+
+            $bResult = $this->executeStatement($oStatement, $aBindings, $bVerbose);
+        }
+
+        if(isset($GLOBALS['bx_profiler']))
+        	$GLOBALS['bx_profiler']->endQuery($bResult);
+
+		//is needed for SILENT mode
+		if(!$bResult && !empty($this->_aError))
+			$this->error($this->_aError);
+
+        return $bResult;
+    }
+	
+	public function ping()
+    {
+    	try {
+    		$this->pdoQuery("SELECT 1");
+    	}
+    	catch (PDOException $e) {
+    		return false;
+    	}
+
+    	return true;
+    }
+	
+	public function pdoQuery($sQuery)
+    {
+    	return self::$_rLink->query($sQuery);
+    }
+	
+	public function pdoExec($sQuery)
+    {
+    	return self::$_rLink->exec($sQuery);
+    }
+	
+	 public function getOne($oStatement, $aBindings = array(), $iIndex = 0)
+    {
+        if(!$oStatement)
+			return false;
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+		$aResult = array();
+		if($this->res($oStatement, $aBindings))
+            $aResult = $oStatement->fetch(PDO::FETCH_NUM);
+
+        return is_array($aResult) && count($aResult) ? $aResult[$iIndex] : false;
+    }
+	
+	public function prepare($sQuery)
+    {
+    	if(!self::$_rLink)
+    		return false;
+
+        $aArgs = func_get_args();
+        $sQuery = array_shift($aArgs);
+
+        $oStatement = self::$_rLink->prepare($sQuery);
+
+        $iIndex = 1;
+        foreach($aArgs as $mixedArg) {
+        	if(is_null($mixedArg))
+				$iValueType = PDO::PARAM_NULL;
+            else if(is_numeric($mixedArg))
+                $iValueType = PDO::PARAM_INT;
+            else
+                $iValueType = PDO::PARAM_STR;
+
+        	$oStatement->bindValue($iIndex++, $mixedArg, $iValueType);
+        }
+
+        return $oStatement;
+    }
+	
+	protected function executeStatement($oStatement, $aBindings = array(), $bVerbose = null)
+    {
+    	$bResult = false;
+
+    	switch (self::$_rLink->getAttribute(PDO::ATTR_ERRMODE)) {
+    		case PDO::ERRMODE_SILENT:
+    			$bResult = $this->executeStatementSilent($oStatement, $aBindings, $bVerbose);
+    			break;
+
+    		case PDO::ERRMODE_EXCEPTION:
+    			$bResult = $this->executeStatementException($oStatement, $aBindings, $bVerbose);
+    			break;
+    	}
+
+    	return $bResult;
+    }
+
+    protected function executeStatementException($oStatement, $aBindings = array(), $bVerbose = null)
+    {
+    	$bResult = false;
+
+    	try {
+			$bResult = $oStatement->execute(!empty($aBindings) && is_array($aBindings) ? $aBindings : null);
+		}
+		catch (PDOException $oException) {
+			$aError = $oStatement->errorInfo();
+
+			$oException->errorInfo[self::$_sErrorKey] = array(
+				'code' => BX_DB_ERR_QUERY_ERROR,
+				'message' => !empty($aError[2]) ? $aError[2] : $oException->getMessage(),
+				'query' => $oStatement->queryString,
+				'trace' => $oException->getTrace(),
+				'verbose' => $bVerbose
+			);
+
+			throw $oException;
+		}
+
+		return $bResult;
+    }
+
+    protected function executeStatementSilent($oStatement, $aBindings = array(), $bVerbose = null)
+    {
+    	$bResult = $oStatement->execute(!empty($aBindings) && is_array($aBindings) ? $aBindings : null);
+    	if($bResult)
+    		return true;
+
+		$aError = $oStatement->errorInfo();
+
+        $aTrace = debug_backtrace();
+        unset($aTrace[0]);
+
+		$this->_aError = array(
+			'code' => BX_DB_ERR_QUERY_ERROR,
+			'message' => !empty($aError[2]) ? $aError[2] : '',
+			'query' => $oStatement->queryString,
+			'trace' => $aTrace,
+			'verbose' => $bVerbose
+		);
+
+		return false;
+    }
+	
+	function getRow($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
+    {
+    	$aResult = array();
+        if(!$oStatement)
+            return $aResult;
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+        if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
+            $iFetchType = $this->_iPdoFetchType;
+
+        if(!$this->res($oStatement, $aBindings))
+        	return array();
+
+		$aResult = $oStatement->fetch($iFetchType);
+		if($aResult === false)
+        	return array();
+
+        return $aResult;
+    }
+	
+	public function query($oStatement, $aBindings = array(), $bVerbose = null)
+    {
+    	if(!$oStatement)
+            return false;
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+        if($this->res($oStatement, $aBindings, $bVerbose))
+            return $oStatement->rowCount();
+
+        return false;
+    }
+	
+	public function getAll($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
+    {
+    	$aResult = array();
+        if(!$oStatement)
+            return $aResult;
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+        if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
+            $iFetchType = $this->_iPdoFetchType;
+
+        if(!$this->res($oStatement, $aBindings))
+        	return array();
+
+		$aResult = $oStatement->fetchAll($iFetchType);
+		if($aResult === false)
+        	return array();
+
+        return $aResult;
+    }
+
+    public function getPairs($oStatement, $sFieldKey, $sFieldValue, $aBindings = array())
+    {
+    	$aResult = array();
+        if(!$oStatement)
+            return $aResult;
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+        $aRow = $this->getFirstRow($oStatement, $aBindings, PDO::FETCH_ASSOC);
+        while(!empty($aRow)) {
+        	$aResult[$aRow[$sFieldKey]] = $aRow[$sFieldValue];
+
+        	$aRow = $this->getNextRow(PDO::FETCH_ASSOC);
+        }
+
+        return $aResult;
+    }
+	
+	public function getFirstRow($oStatement, $aBindings = array(), $iFetchType = PDO::FETCH_ASSOC)
+    {
+        if(!$oStatement)
+            return array();
+		else if(!($oStatement instanceof PDOStatement) && is_string($oStatement))
+			$oStatement = $this->prepare($oStatement);
+
+        if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
+            $iFetchType = $this->_iPdoFetchType;
+
+        if(!$this->res($oStatement, $aBindings)) 
+        	return array();
+
+        $aResult = $oStatement->fetch($iFetchType);
+        if($aResult === false)
+        	return array();
+
+		$this->_oStatement = $oStatement;
+        return $aResult;
+    }
+
+    public function getNextRow($iFetchType = PDO::FETCH_ASSOC)
+    {
+    	if(!$this->_oStatement)
+            return array();
+
+		if(!in_array($iFetchType, array(PDO::FETCH_NUM, PDO::FETCH_ASSOC, PDO::FETCH_BOTH)))
+            $iFetchType = $this->_iPdoFetchType;
+
+		$aResult = $this->_oStatement->fetch($iFetchType);
+		if($aResult !== false)
+			return $aResult;
+
+		$this->_oStatement = null;
+    	return array();
+    }
+		
+	public function isTableExists($sTable){
+		return !empty($this -> getRow("SHOW TABLES LIKE '" . $sTable . "'"));
+   }
+
+   public function getSEParam($sTable, $sName){
+		if (!$this -> isTableExists($sTable)) return '';
+		
+		$sQuery = $this -> prepare("SELECT `value` FROM `{$sTable}` WHERE `name` = ?", $sName);
+		return $this -> getOne($sQuery);
+   }
+
+}
+
+
+/** @} */

+ 29 - 0
modules/boonex/se_migration/classes/BxSEMigAlertsResponse.php

@@ -0,0 +1,29 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxSEMigAlertsResponse extends BxBaseModTextAlertsResponse
+{
+    public function __construct()
+    {
+        $this -> MODULE = 'bx_se_migration';
+        parent::__construct();
+    }
+
+    public function response($oAlert)
+    {
+        if ('system' == $oAlert->sUnit && 'encrypt_password_after' == $oAlert->sAction)
+            BxDolService::call($this->MODULE, 'encrypt_password', array($oAlert));
+
+        parent::response($oAlert);
+    }
+}
+
+/** @} */

+ 194 - 0
modules/boonex/se_migration/classes/BxSEMigBlogs.php

@@ -0,0 +1,194 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxSEMigData.php');
+bx_import('BxDolStorage');
+	
+class BxSEMigBlogs extends BxSEMigData {	
+	public function BxSEMigBlogs (&$oMigrationModule, &$oSE) {
+        parent::BxSEMigData($oMigrationModule, $oSE);
+    }    
+	
+	public function getTotalRecords(){
+		return (int)$this -> _seDb -> getOne("SELECT COUNT(*) FROM `{$this -> _sEnginePrefix}blog_blogs`");			
+	}
+	
+	public function runMigration () {        
+		if (!$this -> getTotalRecords()){
+			  $this -> setResultStatus(_t('_bx_se_migration_no_data_to_transfer'));
+	          return SUCCESSFUL;
+		}	
+		
+		$this -> setResultStatus(_t('_bx_se_migration_started_migration_blogs'));
+		
+		$aResult = $this -> _seDb -> getAll("SELECT * FROM `{$this -> _sEnginePrefix}blog_blogs` ORDER BY `blog_id`");		
+		
+		$iComments = 0;		
+		foreach($aResult as $iKey => $aValue){ 
+			$iProfileId = $this -> getProfileId((int)$aValue['owner_id']);			
+			if (!$iProfileId || $this -> isBlogExisted($iProfileId, $aValue['title'])) continue;
+			
+			$sQuery = $this -> _oDb -> prepare( 
+                     "
+                     	INSERT INTO
+                     		`bx_posts_posts`
+                     	SET
+                     		`author`   			= ?,
+                     		`added`      		= ?,
+                     		`changed`   		= ?,
+							`thumb`				= 0,
+							`title`				= ?,		
+                     		`text`				= ?,
+							`views`				= ?,
+							`comments`			= ?,
+							`status`			= 'active',
+							`cat`				= ?	
+                     ", 
+						$iProfileId, 
+						isset($aValue['creation_date']) ? strtotime($aValue['creation_date']) : time(), 
+						isset($aValue['modified_date']) ? strtotime($aValue['modified_date']) : time(), 
+						isset($aValue['title']) && $aValue['title'] ? $aValue['title'] : '',
+						isset($aValue['body']) && $aValue['body'] ? $aValue['body'] : '',
+						(int)$aValue['view_count'],
+						(int)$aValue['comment_count'],
+						(int)$aValue['category_id']
+						);			
+		
+			$this -> _oDb -> query($sQuery);
+			
+			$iBlogId = $this -> _oDb -> lastId();					
+			
+			if (!$iBlogId){
+				$this -> setResultStatus(_t('_bx_se_migration_started_migration_blogs_error', (int)$aValue['blog_id']));
+	            return FAILED;
+			}	
+
+			$iComments += $this -> transferComments((int)$aValue['blog_id'], $iBlogId, $iProfileId);			
+			$this -> _iTransferred++;
+         }      	
+        
+		$iCategories = 0;
+		if ($this -> _iTransferred) $iCategories = $this -> transferCategories();
+
+        // set as finished;
+        $this -> setResultStatus(_t('_bx_se_migration_started_migration_blogs_finished', $this -> _iTransferred, $iComments, $iCategories));
+        return SUCCESSFUL;
+    }
+
+   /**
+	* Returns Post's plugin Languages Category ID
+	* @return Integer
+         */      
+	private function getPostsCategoryId(){
+		return (int)$this -> _oDb -> getOne("SELECT `ID` FROM `sys_localization_categories` WHERE `Name` = 'Posts' LIMIT 1");
+	}
+	
+	/**
+	* Check if category already transferred
+	* @param int $iID category ID
+	* @param string $sName language key	
+	* @return Boolean
+         */  
+	private function isCategoryExisted($iID, $sName){		
+		$sQuery = $this -> _oDb -> prepare("SELECT COUNT(*) FROM `sys_form_pre_values` WHERE `Value` = ? AND `LKey` = ? LIMIT 1", $iID, $sName);
+		return (int)$this -> _oDb -> getOne($sQuery) == 1;
+	}
+	
+	/**
+	* Transfers all categories
+	* @return Integer
+         */ 
+	private function transferCategories(){
+    	$oLanguage = BxDolStudioLanguagesUtils::getInstance();
+		$iPostsCategoryId = $this -> getPostsCategoryId(); 
+		
+		/* get all blogs categories from SE*/
+		$aSECategories = $this -> _seDb -> getPairs("SELECT * FROM  `{$this -> _sEnginePrefix}blog_categories` ORDER BY `category_id` ASC", 'category_id', 'category_name');
+		if (empty($aSECategories) || !$iPostsCategoryId) return false;
+		
+		/*  remove all current posts categories */
+		$aCategories = $this -> _oDb -> getPairs("SELECT * FROM  `sys_form_pre_values` WHERE `Key` = 'bx_posts_cats'  AND `Value` <> '' ORDER BY `Order` ASC", 'Value', 'LKey');			
+		foreach($aCategories as $iKey => $sValue){ 
+			$oLanguage -> deleteLanguageString($sValue);
+			$this -> _oDb -> query("DELETE FROM `sys_form_pre_values` WHERE `Key` = 'bx_posts_cats' AND `LKey` = '{$sValue}'");
+		}						
+		
+		/*  transfer  blogs categories  from Social Engine */
+		$iTransferred  = 0;	
+		foreach($aSECategories as $iKey => $sValue){ 
+			$sValue = bx_process_input($sValue);				
+			if ($this -> isCategoryExisted($iKey, $sValue)) continue;
+				$sKey = '_bx_posts_cats_' . $sValue;
+															
+				$sQuery = $this -> _oDb -> prepare("INSERT INTO `sys_form_pre_values` SET 
+													`Key` = 'bx_posts_cats', 
+													`Value` = ?, 
+													`Order` = ?, 
+													`LKey` = ?", 
+													$iKey, 
+													$iKey, 
+													$sKey);
+                /*add new Language Keys*/
+				$oLanguage -> addLanguageString($sKey, $sValue, 0, $iPostsCategoryId);										
+				if ($this -> _oDb -> query($sQuery)) $iTransferred++;
+		}		   
+		
+		if ($iTransferred) 
+		
+		return $iTransferred;
+   }
+   /**
+	* Check if Blog already transferred  by Author ID and Blog's Title
+	* @param int $iAuthor social engine's profile ID
+	* @param string $sTitle blog's title
+	* @return Boolean
+         */     
+    private function isBlogExisted($iAuthor, $sTitle){    	
+		$sQuery  = $this -> _oDb ->  prepare("SELECT COUNT(*) FROM `bx_posts_posts` WHERE `author` = ? AND `title` = ? LIMIT 1", $iAuthor, $sTitle);
+        return (bool)$this -> _oDb -> getOne($sQuery);
+    }
+   
+   /**
+	* Transfer all Blogs' Comments
+	* @param int $iOldBlogId social engine's blog's ID
+	* @param int $iNewBlogId una post's ID		
+	* @return Integer
+         */  	
+	private function transferComments($iOldBlogId, $iNewBlogId){
+		$aComments = $this -> _seDb -> getAll("SELECT * FROM  `{$this -> _sEnginePrefix}core_comments` WHERE `resource_type` = 'blog' AND `resource_id` = '{$iOldBlogId}' AND  `poster_type` = 'user' ORDER BY `comment_id` ASC");
+		if (empty($aComments)) return false;
+		
+		$iComments = 0;
+		foreach($aComments as $iKey => $aComment){			
+			$iProfileId = $this -> getProfileId($aComment['poster_id']);
+			$sQuery = $this -> _oDb -> prepare("INSERT INTO `bx_posts_cmts` SET
+										`cmt_object_id`		= ?,
+										`cmt_author_id`		= ?,
+										`cmt_text`			= ?,
+										`cmt_rate_count`	= ?,	
+										`cmt_time`			= ?
+									", 
+									$iNewBlogId,
+									$iProfileId,
+									bx_process_input($aComment['body']),
+									(int)$aComment['like_count'],
+									isset($aComment['creation_date']) ? strtotime($aComment['creation_date']) : time()
+									); 
+			
+			$this -> _oDb -> query($sQuery);
+			$iComments++;
+		}
+		
+		return $iComments;
+	}	
+}
+
+?>

+ 89 - 0
modules/boonex/se_migration/classes/BxSEMigConfig.php

@@ -0,0 +1,89 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup   Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxSEMigConfig extends BxBaseModTemplateConfig {
+	/**
+	* @$_aMigrationModules modules's data for transferring array
+	* @section comments Comments
+	* @ref BxDolCmts
+	*/
+	public $_aMigrationModules = array(
+               'profiles' => array(
+                    'table_name'     => 'users',
+                    'migration_class' => 'BxSEMigProfiles',
+            		'dependencies' => array(
+			        ),
+					'plugins' => array(
+						'bx_persons' => 'Persons',
+			        ),
+                ),
+				'profile_fields' => array(
+                    'table_name'		=> 'user_fields_meta',
+                    'migration_class'	=> 'BxSEMigProfilesFields',
+			        'dependencies' => array(
+                        'profiles',
+                    ),
+					'plugins' => array(
+						'bx_persons' => 'Persons',
+			        ),
+                ),
+				
+				'blogs' => array(
+                    'table_name'		=> 'blog_blogs',
+                    'migration_class'	=> 'BxSEMigBlogs',
+			        'dependencies' => array(
+                		'profiles',
+                     ),
+					 'plugins' => array(
+						'bx_persons'	=> 'Persons',
+						'bx_posts'		=> 'Posts'					
+			        ),
+                ),
+				
+				'photos' => array(
+                    'table_name'		=> 'album_albums',
+                    'migration_class' 	=> 'BxSEMigPhotoAlbums',
+			        'dependencies' => array(
+                		'profiles',
+                     ),
+					 'plugins' => array(
+						'bx_persons'	=> 'Persons',
+						'bx_albums'		=> 'Albums'	
+			        ),
+                ),
+                
+                /*'videos' => array(
+                    'table_name'     => 'video_videos',
+                    'migration_class' => 'BxSEMigVideos',
+			        'dependencies' => array(
+                		'profiles',
+                     ),
+					 'plugins' => array(
+						'bx_persons'	=> 'Persons',
+						'bx_albums'		=> 'Albums'	
+			        ),
+                ),*/                
+             );
+			 
+   public function __construct($aModule){
+        parent::__construct($aModule); 
+		
+		$this -> CNF = array (
+            'ENGINE_V' => 'se_migration_version',		
+		);
+   }
+
+   public function getEngineVersionPrefix(){
+		return getParam($this -> CNF['ENGINE_V']) . '_';
+   }   
+}
+
+/** @} */

+ 72 - 0
modules/boonex/se_migration/classes/BxSEMigData.php

@@ -0,0 +1,72 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+ 
+define('SUCCESSFUL', 1);
+define('FAILED', 0);
+ 
+class BxSEMigData
+{      
+	protected $_oMainModule;
+	protected $_seDb;
+	protected $_sPrefix;
+	protected $_oDb;
+	protected $_sEnginePrefix = 'engine4_';
+	protected $_iTransferred = 0;
+
+	public function BxSEMigData(&$oMainModule, &$seDb)
+	{
+	     $this -> _sPrefix = $oMainModule -> _aModule['db_prefix'];
+	     $this -> _oMainModule = $oMainModule;
+	     $this -> _seDb  = $seDb;
+	     $this -> _oDb = $this -> _oMainModule -> _oDb;
+		 $this -> _sEnginePrefix = $this -> _oMainModule -> _oConfig -> getEngineVersionPrefix();
+	}
+
+
+
+	/**
+	* Performs Migration Data
+	* @return boolean
+	*/
+	public function runMigration(){
+	    $this -> setResultStatus(_t('_bx_se_migration_define_migration_method'));
+	    return FAILED;
+	}
+
+	/**
+	* Gets total records for transferring
+	* @return boolean
+	*/
+	public function getTotalRecords(){
+	    $this -> setResultStatus(_t('_bx_se_migration_define_total_method'));
+	    return FAILED;		
+	}
+
+	/**
+	* Set Migration Status
+	* @param string $sStatus message
+	*/         
+	protected function setResultStatus($sStatus){
+	    $sQuery = $this -> _oDb -> prepare("UPDATE `{$this -> _sPrefix}transfers` SET `status_text` = ? WHERE `module` = ? ", $sStatus, $this -> _oMainModule -> sProcessedModule);
+	    $this -> _oDb -> query($sQuery);
+	}
+
+	/**
+	* Returns transferred Una's profile id of transferred Social Engine's member
+	* @param int $iSEId social engine's profile ID
+	* @return Integer
+	*/           
+	protected function getProfileId($iSEId){
+	    return (int)$this -> _oDb -> getOne("SELECT `profile_id` FROM  `sys_accounts` WHERE  `se_id` =  '{$iSEId}' LIMIT 1");       
+	}
+}
+   
+/** @} */

+ 136 - 0
modules/boonex/se_migration/classes/BxSEMigDb.php

@@ -0,0 +1,136 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolDb');
+
+class BxSEMigDb extends BxBaseModGeneralDb
+{	
+	public function __construct(&$oConfig)
+	{
+		parent::__construct($oConfig);			
+	}
+
+	/** 
+	* Saving Social Engine config info
+	* @param array $aConfig parameters
+	*/ 
+	public function saveConfig($aConfig){
+		if( is_array($aConfig) ) {
+            
+			$bRecords = false;
+			foreach($aConfig as $sName => $mValue)
+            {
+                $mValue = bx_process_input($mValue);
+                $sName  = bx_process_input($sName);
+				$sQuery = $this -> prepare("REPLACE INTO `{$this -> _sPrefix}config` SET `name` = ?, `value` = ?", $sName, $mValue);
+				if ($this -> res($sQuery)) $bRecords = true;
+            }			
+      }
+	} 
+	
+	/** 
+	* Get Social Engine config parameter
+	* @param string $sConfigName param's name 
+	* @return string 
+	*/
+	public function getExtraParam($sConfigName){ 
+		$sQuery = $this -> prepare("SELECT `value` FROM `{$this -> _sPrefix}config` WHERE `name` = ?", $sConfigName);
+		return $this -> getOne($sQuery);
+	}
+
+	/** 
+	* Create migration list
+	* @param array $aConfig social engine config info 
+	*/	
+	public function createConfig($aConfig){
+        if( is_array($aConfig) ) {
+            foreach($aConfig as $sName => $mValue)
+            {
+                $mValue = $this -> escape($mValue);
+                $sName  = $this -> escape($sName);
+
+                // create new;
+                if( !$this -> getExtraParam($sName) ) {
+                    $sQuery = "INSERT INTO `{$this -> _sPrefix}config` SET `name` = '{$sName}', `value` = '{$mValue}'";
+                    $this -> query($sQuery);
+                }
+                else {
+                // update exsisting;
+                    $sQuery = "UPDATE `{$this -> _sPrefix}config` SET  `value` = '{$mValue}' WHERE `name` = '{$sName}'"; 
+                    $this -> query($sQuery);
+                }
+            }
+        }
+	}
+	
+	/** 
+	* Returns Migration Result information
+	* @param string $sModule name
+	* @return  string
+	*/
+	public function getTransferStatusText($sModule)
+    {
+        $sQuery = $this -> prepare("SELECT `status_text` FROM `{$this -> _sPrefix}transfers` WHERE `module` = ? LIMIT 1", $sModule);
+        return $this -> getOne($sQuery);
+    }
+
+	/** 
+	* Returns current migration status
+	* @param string $sModule name
+	* @return  string
+	*/
+	 public function getTransferStatus($sModule)
+	 {
+	     $sQuery = $this -> prepare("SELECT `status` FROM `{$this -> _sPrefix}transfers` WHERE `module` = ? LIMIT 1", $sModule);
+	     return $this -> getOne($sQuery);
+	 }
+
+	 public function updateTransferStatus($sModule, $sStatus){            
+	     $this -> query("UPDATE `{$this -> _sPrefix}transfers` SET `status` = :status WHERE `module` = :module", array('module' => $sModule, 'status' => $sStatus));
+	 }
+
+	/** 
+	* Check if social engine configuration exists
+	* @return  Boolean
+	*/
+	 public function isConfigInstalled(){
+		return $this -> getExtraParam('dbname') && $this -> getExtraParam('username');
+	 }
+	
+	/** 
+	* Adds module for migration to migration list
+	* @param string $sName modules name
+	* @param int $iNumber records number
+	* @return mixed 
+	*/		
+	public function addToTransferList($sName, $iNumber){
+		return $this -> query("REPLACE INTO `{$this -> _sPrefix}transfers` SET `module` = :module, `status` = 'not_started', `number` = :number", array('module' => $sName, 'number' => $iNumber));	
+	}		
+
+	public function getTransfers(){
+		return $this -> getAll("SELECT * FROM `{$this -> _sPrefix}transfers` ORDER BY `id`");
+	}
+
+	/** 
+	* Check whether requiered Una plugin installed for transferring
+	* @param string $sName pugin name
+	* @return Boolean
+	*/			
+	public function isPluginInstalled($sName){
+		$sQuery = $this -> prepare("SELECT COUNT(*) FROM `sys_modules` WHERE `type` = 'module' AND `name` = ? LIMIT 1" , $sName);
+		return (int)$this -> getOne($sQuery) == 1;
+	}
+	
+	public function encryptPassword($sPwd, $sSalt){
+		return md5($this -> getParam('se_migration_salt') . $sPwd. $sSalt);
+   }
+}
+/** @} */

+ 135 - 0
modules/boonex/se_migration/classes/BxSEMigModule.php

@@ -0,0 +1,135 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxSEDb.php'); 
+if ( function_exists('ini_set')) {
+    ini_set('max_execution_time', 0);
+}
+	
+define('_ENGINE', 1); //defined to allow including Social Engine config file 
+
+class BxSEMigModule extends BxBaseModGeneralModule
+{
+    protected $_oSEDb = null;			
+	public function __construct(&$aModule){
+        parent::__construct($aModule);       
+    }		
+	
+	public function actionStartTransfer($aModules){
+		if (empty($aModules)){
+			echo json_encode(array('code' => 1, 'message' => _t('_bx_se_migration_successfully_finished')));
+			exit;
+		}
+		
+		$this -> initSEDb();		
+		header('Content-Type:text/javascript');	
+		
+		
+		foreach($aModules as $iKey => $sModule){
+            if( $sModule && !empty(($this -> _oConfig -> _aMigrationModules[$sModule]))) {
+				
+				$sTransferred = $this -> _oDb -> getTransferStatus($sModule);	
+				if ($sTransferred == 'finished') continue;
+		             
+				if(is_array($this -> _oConfig -> _aMigrationModules[$sModule]['dependencies'])) {
+						foreach($this -> _oConfig -> _aMigrationModules[$sModule]['dependencies'] as $iKey => $sDependenciesModule)
+	                    {
+	                        $sTransferred = $this -> _oDb -> getTransferStatus($sDependenciesModule);							
+	                        if( $sTransferred != 'finished')
+								return _t('_bx_se_migration_install_before', $sDependenciesModule);                        
+	                   }					   
+	             }
+				 
+				 if(is_array($this -> _oConfig -> _aMigrationModules[$sModule]['plugins'])) {
+						$sPlugins = '';
+						foreach($this -> _oConfig -> _aMigrationModules[$sModule]['plugins'] as $sKey => $sTitle){
+	                        if (!$this -> _oDb -> isPluginInstalled($sKey)) 	                                      								
+								$sPlugins .= $sTitle . ', ';								
+	                    }
+						
+						if ($sPlugins)
+							return _t('_bx_se_migration_install_plugin', trim($sPlugins, ', '), $sModule);														
+	             }		 
+				 
+					
+                // create new module's instance;
+				require_once($this -> _oConfig -> _aMigrationModules[$sModule]['migration_class'] . '.php');
+				$this -> sProcessedModule = $sModule;
+		
+				// set as started;
+                $this -> _oDb -> updateTransferStatus($sModule, 'started');
+				
+                 // create new migration instance;
+                $oModule = new $this -> _oConfig -> _aMigrationModules[$sModule]['migration_class']($this, $this -> _oSEDb);
+                if($oModule -> runMigration()) 
+	                $this -> _oDb -> updateTransferStatus($sModule, 'finished');                
+                else {                    
+                    $this -> _oDb -> updateTransferStatus($sModule, 'error');
+					return _t('_bx_se_migration_successfully_failed');					
+                }
+            }		
+	     }
+		
+		return _t('_bx_se_migration_successfully_finished');	
+	}
+	
+	/** 
+	* Creates Migration Info before to start with number of records
+	* @param ref $oSEDb social engine database connect
+	*/
+	public function createMigration(){
+		if (is_null($this -> _oSEDb)) $this -> initSEDb();
+		
+		foreach ($this -> _oConfig -> _aMigrationModules as $sName => $aModule){			
+			if ($this -> _oSEDb -> isTableExists($this -> _oConfig -> getEngineVersionPrefix() . $aModule['table_name'])){
+				
+				//Create transferring class object
+				require_once($aModule['migration_class'] . '.php');			
+				$oObject = new $aModule['migration_class']($this, $this -> _oSEDb);			
+				
+				if ($iNumber = $oObject -> getTotalRecords()) 
+							$this -> _oDb -> addToTransferList($sName, $iNumber);		
+			}	
+		}
+	}
+	
+	/** 
+	* Inits connect with Social Engine database
+	* @return Boolean
+	*/	
+	public function initSEDb(){
+		$aConfig = array(
+                'host'    => $this -> _oDb -> getExtraParam('host'),
+                'user'	  => $this -> _oDb -> getExtraParam('username'),
+            	'pwd'     => $this -> _oDb -> getExtraParam('password'),
+                'name' 	  => $this -> _oDb -> getExtraParam('dbname'),
+				'charset' => $this -> _oDb -> getExtraParam('charset'),				
+				'port'    => '',
+				'sock'	  => ''	
+			);
+					
+		$this -> _oSEDb = new SEDB($aConfig);		
+		return $this -> _oSEDb	-> connect(); 
+	}
+	
+	/** 
+	* Returns Password Ecnrypted by Seocial Engine Rules
+	* @param object	
+	*/	
+	public function serviceEncryptPassword($oAlert){
+		// if imported member tries to login set new hash for password
+		if (isset($oAlert -> aExtras['info']['se_id']) && (int)$oAlert -> aExtras['info']['se_id']){
+			$oAlert -> aExtras['password'] = $this -> _oDb -> encryptPassword($oAlert -> aExtras['pwd'], $oAlert -> aExtras['info']['salt']);
+		}		
+	}
+}
+
+/** @} */

+ 134 - 0
modules/boonex/se_migration/classes/BxSEMigPhotoAlbums.php

@@ -0,0 +1,134 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once('BxSEMigData.php');
+bx_import('BxDolStorage');
+	
+class BxSEMigPhotoAlbums extends BxSEMigData {		
+	private $_iTransferredAlbums;
+    
+	public function BxSEMigPhotoAlbums (&$oMigrationModule, &$oSE) {
+        parent::BxSEMigData($oMigrationModule, $oSE);
+    }
+	
+	public function getTotalRecords(){
+		return (int)$this -> _seDb -> getOne("SELECT SUM(`count`) FROM (
+																		SELECT COUNT(*) as `count` FROM `{$this -> _sEnginePrefix}storage_files` 
+																		WHERE  `parent_file_id` IS NULL AND `parent_type` IN ('album_photo', 'user') GROUP BY `parent_id`
+																		) as `c`");
+	}
+    
+	public function runMigration () {
+		if (!$this -> getTotalRecords()){
+			  $this -> setResultStatus(_t('_bx_se_migration_no_data_to_transfer'));
+	          return SUCCESSFUL;
+		}
+		
+		$this -> setResultStatus(_t('_bx_se_migration_started_migration_photos'));
+
+		$aResult = $this -> _seDb -> getAll("SELECT * FROM `{$this -> _sEnginePrefix}album_albums` WHERE `type` = 'profile' ORDER BY `album_id`");
+		foreach($aResult as $iKey => $aValue){ 
+			
+			$iProfileId = $this -> getProfileId((int)$aValue['owner_id']);
+			if (!$iProfileId) continue;
+			
+			$sAlbumTitle = isset($aValue['title']) && $aValue['title'] ? $aValue['title'] : 'Profile Photos';
+			
+			if (!$this -> isAlbumExisted($iProfileId, $sAlbumTitle)){
+				$sQuery = $this -> _oDb -> prepare( 
+	                     "
+	                     	INSERT INTO
+	                     		`bx_albums_albums`
+	                     	SET
+	                     		`author`   			= ?,
+	                     		`added`      		= ?,
+	                     		`changed`   		= ?,
+								`thumb`				= 0,
+								`title`				= ?,		
+	                     		`text`				= ?,
+								`views`				= ?					
+	                     ", 
+							$iProfileId, 
+							isset($aValue['creation_date']) ? strtotime($aValue['creation_date']) : time(), 
+							isset($aValue['modified_date']) ? strtotime($aValue['modified_date']) : time(), 
+							isset($aValue['title']) && $aValue['title'] ? $aValue['title'] : 'Profile Photos',
+							isset($aValue['description']) && $aValue['description'] ? $aValue['description'] : '',
+							(int)$aValue['view_count']
+							);			
+			
+				$this -> _oDb -> query($sQuery);
+				$iAlbumId = $this -> _oDb -> lastId();					
+				if (!$iAlbumId){
+					$this -> setResultStatus(_t('_bx_se_migration_started_migration_photos_album_error'));
+		            return FAILED;
+				}	
+			
+			$this -> migrateAlbumPhotos($aValue['owner_id'], $iProfileId, $iAlbumId);	
+			
+			}
+	
+			$this -> _iTransferredAlbums++;
+       }        
+
+        // set as finished;
+        $this -> setResultStatus(_t('_bx_se_migration_started_migration_photos_finished', $this -> _iTransferred));
+        return SUCCESSFUL;
+    }
+   	
+   /**
+	* Migrates all photo albums and users photos
+	* @param int $iUserId social engine's profile ID
+	* @param int $iProfileId una profile ID
+	* @param int $iAlbumID created una Album		
+	* @return Integer
+         */  
+   private function migrateAlbumPhotos($iUserId, $iProfileId, $iAlbumID){
+       		$aResult = $this -> _seDb -> getAll("SELECT * FROM  `{$this -> _sEnginePrefix}storage_files` WHERE  `parent_file_id` IS NULL AND  `parent_type` IN ('album_photo', 'user') AND `parent_id` = {$iUserId} ORDER BY `file_id` ASC");
+
+			$iTransferred  = 0;			
+			foreach($aResult as $iKey => $aValue){ 
+				if ($this -> isFileExisted($iProfileId, $aValue['name'])) continue;
+				
+				$sOldImagePath = $this -> _oDb -> getExtraParam('root') . $aValue['storage_path'];
+				
+				if (file_exists($sOldImagePath)){
+					$oStorage = BxDolStorage::getObjectInstance('bx_albums_files'); 
+					$iId = $oStorage -> storeFileFromPath($sOldImagePath, false, $iProfileId); 
+				
+				if ($iId){ 
+						$sQuery = $this -> _oDb -> prepare("INSERT INTO `bx_albums_files2albums` SET `content_id` = ?, `file_id` = ?, `data` = ?, `title` = ?", $iAlbumID, $iId, $this -> getFileSize($sOldImagePath), $aValue['name']);
+	                    $this -> _oDb -> query($sQuery);								
+						$this -> _iTransferred++;
+						$iTransferred++;
+					}
+				}
+		   }
+	  
+	  return $iTransferred;
+   }
+   
+    private function isAlbumExisted($iAuthor, $sTitle){    	
+    	$sQuery  = $this -> _oDb ->  prepare("SELECT COUNT(*) FROM `bx_albums_albums` WHERE `author` = ? AND `title` = ? LIMIT 1", $iAuthor, $sTitle);
+        return (bool)$this -> _oDb -> getOne($sQuery);
+    }
+	
+	private function isFileExisted($iAuthor, $sTitle){    	
+    	$sQuery  = $this -> _oDb ->  prepare("SELECT COUNT(*) FROM `bx_albums_files` WHERE `profile_id` = ? AND `file_name` = ? LIMIT 1", $iAuthor, $sTitle);
+        return (bool)$this -> _oDb -> getOne($sQuery);
+    }
+    
+    private function getFileSize ($sFile) {
+    	$aInfo = getimagesize($sFile);
+		return $aInfo[0] . 'x' . $aInfo[1];
+    }    
+}
+
+?>

+ 269 - 0
modules/boonex/se_migration/classes/BxSEMigProfiles.php

@@ -0,0 +1,269 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolProfile');
+bx_import('BxDolTranscoderImage');
+bx_import('BxDolStorage');
+	
+require_once('BxSEMigData.php');	
+	
+class BxSEMigProfiles extends BxSEMigData
+{
+    public function BxSEMigProfiles(&$oMigrationModule, &$seDb)
+    {
+		parent::BxSEMigData($oMigrationModule, $seDb);
+    }
+
+	public function getTotalRecords(){
+		return (int)$this -> _seDb -> getOne("SELECT COUNT(*) FROM `{$this -> _sEnginePrefix}users`");			
+	}
+	
+	public function runMigration(){
+		if (!$this -> getTotalRecords()){
+			  $this -> setResultStatus(_t('_bx_se_migration_no_data_to_transfer'));
+	          return SUCCESSFUL;
+		}	
+		
+		$this -> setResultStatus(_t('_bx_se_migration_started_migration_profiles'));
+        $sError = $this -> profilesMigrtion();
+        if($sError) {
+              $this -> setResultStatus($sError);
+              return FAILED;
+        }
+
+		//Get Social Engine Secret Salt
+		$sSecret = $this -> _seDb -> getSEParam("{$this -> _sEnginePrefix}core_settings", 'core.secret');
+		if ($sSecret) $this -> _oDb -> setParam('se_migration_salt', $sSecret);
+		
+        $this -> setResultStatus(_t('_bx_se_migration_started_migration_profiles_finished', $this -> _iTransferred));
+		return SUCCESSFUL;
+    }
+	
+	function profilesMigrtion()
+    {
+			$this -> createSEIdField();
+			
+			// get ID of the latest transferred profile from social engine 
+			$iProfileID = (int)$this -> _oDb -> getOne("SELECT `se_id` FROM `sys_accounts` WHERE `se_id` <> 0 ORDER BY `id` DESC LIMIT 1");						
+			$sStart = '';
+			if ($iProfileID) 				
+				$sStart = " WHERE `user_id` > {$iProfileID}";				
+					
+			$aResult = $this -> _seDb -> getAll("SELECT * FROM `{$this -> _sEnginePrefix}users` {$sStart} ORDER BY `user_id`");
+			
+			$oLanguage = BxDolLanguages::getInstance();
+			foreach($aResult as $iKey => $aValue){                  
+
+				  if($aValue['username'] && !$this -> isProfileExisted($aValue['username']) ) {					
+					$aUsersFieldsValues = $this -> getMergedFieldsValues($aValue['user_id']);		
+				
+					$sQuery = $this -> _oDb -> prepare( 
+                     "
+                     	INSERT INTO
+                     		`sys_accounts`
+                     	SET
+                     		`name`   			= ?,
+                     		`email`      		= ?,
+                     		`password`   		= ?,
+							`salt`				= ?,
+							`role`				= '1',		
+                     		`lang_id`			= ?,
+							`added`				= ?,
+                     		`changed`	  		= ?,
+                     		`logged` 			= ?,
+							`email_confirmed`	= '1',
+							`receive_updates`	= '1',	
+							`receive_news`		= '1',
+							`se_id`				=  ?
+							
+                     ", 
+						$aValue['username'], 
+						$aValue['email'], 
+						$aValue['password'],
+						$aValue['salt'],
+						$oLanguage -> getLangId(strtolower($aValue['language'][0].$aValue['language'][1])),
+						isset($aValue['creation_date']) ? strtotime($aValue['creation_date']) : time(),
+						isset($aValue['modified_dat'])? strtotime($aValue['modified_dat']) : time(),
+						isset($aValue['lastlogin_date']) ? strtotime($aValue['lastlogin_date']) : time(),
+						$aValue['user_id']
+						);
+						
+					$this -> _oDb -> query($sQuery);										
+					$iAccountId = $this -> _oDb -> lastId();					
+					if (!$iAccountId) continue;
+										
+					$sQuery = $this -> _oDb -> prepare( 
+	                     "
+	                     	INSERT INTO
+	                     		`bx_persons_data`
+	                     	SET
+	                     		`author`   			= 0,
+	                     		`added`      		= ?,
+	                     		`changed`   		= ?,
+								`picture`			= 0,		
+	                     		`cover`				= 0,
+								`fullname`			= ?,
+	                     		`description`		= ?					
+	                     ", 						
+							isset($aValue['creation_date']) ? strtotime($aValue['creation_date']) : time(),
+							isset($aValue['modified_dat'])? strtotime($aValue['modified_dat']) : time(),
+							isset($aValue['displayname']) ? $aValue['displayname'] : "{$aUsersFieldsValues['first_name']} {$aUsersFieldsValues['last_name']}",
+							isset($aUsersFieldsValues['about_me']) ? $aUsersFieldsValues['about_me'] : ''
+							);
+						
+						$this -> _oDb -> query($sQuery);	
+						$iContentId = $this -> _oDb -> lastId();
+						
+						$this -> _oDb -> query("INSERT INTO `sys_profiles` SET `account_id` = {$iAccountId}, `type` = 'system', `content_id` = {$iContentId}, `status` = 'active'");
+						$this -> _oDb -> query("INSERT INTO `sys_profiles` SET `account_id` = {$iAccountId}, `type` = 'bx_persons', `content_id` = {$iContentId}, `status` = 'active'");
+						$iProfile = $this -> _oDb -> lastId();
+
+						if($iProfile)
+							BxDolAccountQuery::getInstance() -> updateCurrentProfile($iAccountId, $iProfile);
+						
+						$sQuery = $this -> _oDb -> prepare("UPDATE `bx_persons_data` SET `author` = ? WHERE `id` = ?", $iProfile, $iContentId);
+						
+						$this -> _oDb -> query($sQuery);
+
+						
+					if($aValue['photo_id']) $iPictureID = $this -> exportAvatar($iContentId, $aValue['photo_id']);			
+                   
+					 $sResult = $this -> exportFriends($aValue['user_id']);
+                     if($sResult)
+                         return $sResult;                     
+
+                     $this -> _iTransferred++;
+                  }
+             }
+        }
+
+	/**
+	* Create se_id field in sys_accounts for transferring other content conntected with users from social engine
+	* @return mixed
+         */  	  
+	private function createSEIDField(){
+		if ($this -> _oDb -> isFieldExists('sys_accounts', 'se_id')) return true;
+		return $this -> _oDb -> query("ALTER TABLE `sys_accounts` ADD `se_id` int(11) unsigned NOT NULL default '0'");
+	}		
+	
+	/**
+	* Find all fields with the same names and merge fields with the same names into one field with value
+	* @param int $iUserId social engine's user id
+	* @return array
+         */ 	
+	private function getMergedFieldsValues($iUserId){
+		$aValues = $this -> _seDb -> getAll("SELECT * FROM `{$this -> _sEnginePrefix}user_fields_values` WHERE `item_id` = {$iUserId} AND `value` != ''");
+		$aMap = $this -> _seDb -> getPairs("SELECT * FROM `{$this -> _sEnginePrefix}user_fields_meta`", 'field_id', 'type');
+			
+		if (empty($aValues) || empty($aMap)) return array();						
+			
+		$aResult = array();			
+		foreach($aValues as $iKey => $aValue){
+			$aResult[$aMap[$aValue['field_id']]] = $aValue['value'];
+		}
+			
+		return $aResult;
+	}
+
+	/**
+	* Get Avatar's image  path
+	* @param int $iImgId image ID
+	* @return string
+         */ 	
+	private function getProfileImage($iImgId){        	
+       $sQuery = $this -> _seDb -> prepare("SELECT `storage_path` FROM `{$this -> _sEnginePrefix}storage_files` WHERE `file_id` = ? AND `parent_file_id` IS NULL LIMIT 1" , $iImgId);            
+       return $this -> _seDb -> getOne($sQuery);			
+	}
+ 
+	private function exportFriends($iProfileId)
+    {
+       $iProfileId = (int) $iProfileId;
+
+       $sQuery = $this -> _seDb -> prepare("SELECT * FROM `{$this -> _sEnginePrefix}user_membership` WHERE `resource_id` = {$iProfileId} OR `user_id` = {$iProfileId}");
+	   $aFriends = $this -> _seDb -> getAll($sQuery);
+
+		foreach($aFriends as $iKey => $aValue){
+			if( !$this -> isFriendExists($aValue['resource_id'], $aValue['user_id'])) {
+
+			       $sQuery =  $this -> _oDb -> prepare("
+	                 	INSERT IGNORE INTO
+	                    	`sys_profiles_conn_friends`
+	                    SET
+	                       `initiator`	= ?,
+	                       `content`    = ?,
+	                       `mutual`     = ?,
+	                       `added`     	= UNIX_TIMESTAMP()
+			       ", $aValue['resource_id'], $aValue['user_id'], $aValue['active']);
+	
+			       $iResult = (int) $this -> _oDb -> query($sQuery);
+			       if($iResult <= 0) {
+			           return _t('_bx_se_migration_friends_exports_error');
+			       }
+		        }
+				
+			}	    
+	}
+
+	private function exportAvatar($iProfileId, $iAvatarId)
+    {
+        	$iProfileId = (int) $iProfileId;
+        	$iAvatarId = (int) $iAvatarId;
+
+            $sSEAvatar = $this -> getProfileImage($iAvatarId);
+
+            if($sSEAvatar) {
+	            $sSEAvatarPath = $this -> _oDb -> getExtraParam('root') . $sSEAvatar;
+				
+				$oStorage = BxDolStorage::getObjectInstance('bx_persons_pictures'); 
+				
+				$iId = $oStorage->storeFileFromPath($sSEAvatarPath, false, $iProfileId); 
+				if ($iId){ 
+					$sQuery = $this -> _oDb -> prepare("UPDATE `bx_persons_data` SET `Picture` = ? WHERE `id` = ?", $iId, $iProfileId);
+                    $this -> _oDb -> query($sQuery);								
+				}
+            }
+      }
+    
+	/**
+	* Check if profile with the same nickname already exists
+	* @param string $sNickName nameof the user
+	* @return string
+         */ 	 
+	private function isProfileExisted($sNickName)
+	{
+         $sQuery  = $this -> _oDb -> prepare("SELECT COUNT(*) FROM `sys_accounts` WHERE `name` = ?", $sNickName);
+         return $this -> _oDb -> getOne($sQuery) ? true : false;
+	} 
+
+	private function isFriendExists($iProfileId, $iFriendId)
+    {
+            $iProfileId = (int) $iProfileId;
+            $iFriendId  = (int) $iFriendId;
+
+            $sQuery =  "
+            	SELECT 
+            		COUNT(*) 
+            	FROM 
+            		`sys_profiles_conn_friends` 
+            	WHERE
+            		(
+            			(`initiator` = {$iProfileId} AND `content` = {$iFriendId})
+            				OR
+            			(`initiator` = {$iFriendId} AND `content` = {$iProfileId})	
+            		)
+            ";
+
+            return $this -> _oDb -> getOne($sQuery) ? true : false;
+	}
+}
+
+	
+/** @} */

+ 176 - 0
modules/boonex/se_migration/classes/BxSEMigProfilesFields.php

@@ -0,0 +1,176 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     TridentModules
+ *
+ * @{
+ */
+    
+require_once('BxSEMigData.php');
+bx_import('BxDolStudioLanguagesUtils');
+
+class BxSEMigProfilesFields extends BxSEMigData
+{
+	private $_aProfileFields = array();
+	private $_aFiltered = array();	
+
+    public function BxSEMigProfilesFields($oMigrationModule, &$seDb)
+    {
+        parent::BxSEMigData($oMigrationModule, $seDb);  
+		
+		// dont transfer these fields 
+		$this -> _aFiltered = array(
+            'username', 'email', 'password', 'salt', 'language', 'creation_date', 'modified_dat', 'lastlogin_date', 'user_id',
+			'profile_type', 'first_name', 'last_name', 'about_me', 'heading'
+        );
+    }
+
+	public function getTotalRecords(){
+		$aFields = $this -> _seDb -> getAll("SELECT * FROM `{$this -> _sEnginePrefix}user_fields_meta`");			
+		foreach($aFields as $iKey => $aValue){
+			if (in_array($aValue['type'], $this -> _aFiltered)) continue;
+			
+			$this -> _aProfileFields[$aValue['type']] = array(
+		           'name' => $aValue['type'],
+		           'caption' => $aValue['label'],
+				   'id' => $aValue['field_id'],
+		       );
+		}
+		
+		return count($this -> _aProfileFields);
+	}
+	
+	public function runMigration(){		 
+		if (!$this -> getTotalRecords()){
+			  $this -> setResultStatus(_t('_bx_se_migration_no_data_to_transfer'));
+	          return SUCCESSFUL;
+		}	 
+		 	 
+		 $this -> setResultStatus(_t('_bx_se_migration_started_migration_profile_fields'));		
+		 $oLanguage = BxDolStudioLanguagesUtils::getInstance();
+		 
+		 $i = 0;
+		 foreach($this -> _aProfileFields as $sKey => $aItem){
+	        if (!$this -> _oDb -> isFieldExists('bx_persons_data', $sKey)){
+					$sQuery = $this -> _oDb -> prepare("ALTER TABLE `bx_persons_data` ADD $sKey varchar(255) default ''");               
+               
+				   if (!$this -> _oDb -> query($sQuery)){
+						 $this -> setResultStatus(_t('_bx_se_migration_started_migration_profile_field_can_not_be_transferred'));
+						 return FAILED;				
+				   }			   
+			}
+			   
+			if ($this -> isFieldTransfered($sKey)) continue;
+			   
+			$iKeyID = time() + $i++; // unique language keys postfix
+			
+			$sQuery = $this -> _oDb -> prepare("
+			INSERT INTO `sys_form_inputs` SET
+					`object`	= 'bx_person', 
+					`module`	= 'custom',
+					`name`		= ?,	
+					`type`		= 'text',
+					`caption_system` = ?,
+					`caption`	= ?", $sKey, '_sys_form_txt_field_caption_system_' . $iKeyID, '_sys_form_txt_field_caption_' . $iKeyID);
+
+						
+			if (!$this -> _oDb -> query($sQuery)){
+				$this -> setResultStatus(_t('_bx_se_migration_started_migration_profile_field_can_not_be_transferred'));
+				return FAILED;				
+			}			   
+			
+			// create form fields	
+			$this -> _oDb -> query($sQuery);
+			
+			// add display for view and add forms
+			$this -> _oDb -> query("INSERT INTO `sys_form_display_inputs` (`id`, `display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+									(NULL, 'bx_person_add', '{$sKey}', 2147483647, 1, " . $i . "),
+									(NULL, 'bx_person_view', '{$sKey}', 2147483647, 1, " . $i . ")");
+			// add lanungage keys with translations						
+				$oLanguage -> addLanguageString('_sys_form_txt_field_caption_system_' . $iKeyID, $aItem['caption'], 0, 0, false);
+				$oLanguage -> addLanguageString('_sys_form_txt_field_caption_' . $iKeyID, $aItem['caption']);
+
+				$this -> _iTransferred++;
+	        }
+	          
+			$this -> migrateProfileFieldsInfo(); 
+			
+	        $this -> setResultStatus(_t('_bx_se_migration_started_migration_profile_fields_finished', $this -> _iTransferred));
+	        return SUCCESSFUL;
+	    }
+
+		private function isFieldTransfered($sName){
+			$sQuery = $this -> _oDb -> prepare("SELECT COUNT(*) FROM `sys_form_inputs` WHERE `name` = ? LIMIT 1", $sName);
+			return (int)$this -> _oDb -> getOne($sQuery) > 0;
+		}		
+
+		/*private function getProfileId($iSEId){
+			return (int)$this -> _oDb -> getOne("SELECT `p`.`content_id` FROM  `sys_accounts` AS  `a` 
+																	LEFT JOIN  `sys_profiles` AS  `p` ON `a`.`id` =  `p`.`account_id` 
+																	WHERE  `se_id` =  '{$iSEId}' AND  `p`.`type` =  'bx_persons' LIMIT 1");		
+		}*/
+        
+		/**
+		* Returns social engine profile fields list
+		* @param int $iUserId social engine's user ID
+		* @return array
+		*/ 
+		private function getFieldsValues($iUserId){
+			return $this -> _seDb -> getPairs("SELECT * FROM `{$this -> _sEnginePrefix}user_fields_values` WHERE `item_id` = {$iUserId} AND `value` != ''", 'field_id', 'value');			
+		}
+		
+		/**
+		* Returns Una profile fields list
+		* @param int $iProfileId profile ID
+		* @return array
+		*/ 
+		private function getUsersFieldsValues($iProfileId){
+			return $this -> _oDb -> getRow("SELECT * FROM `bx_persons_data` WHERE `id` = {$iProfileId} LIMIT 1");			
+		}		
+		
+		/**
+		* Transfers profile fields values for the all profiles
+		*/ 		 
+		private function migrateProfileFieldsInfo()
+        {
+            $sQuery = "SELECT * FROM `{$this -> _sEnginePrefix}users`";
+            $aFields = $this -> _seDb -> getAll($sQuery);	
+		
+			foreach($aFields as $iKey => $aValue){
+               if (!($iProfileId =  $this -> getProfileId($aValue['user_id']))) continue;
+			   
+			   if (!empty($aValue['wp_data'])) $aValues = @unserialize($aValue['wp_data']);
+		       
+                $sUpdateField = '';
+				$aFieldsValues = $this -> getFieldsValues($aValue['user_id']);
+				
+				$aCurrentMembersValues = $this -> getUsersFieldsValues($iProfileId);
+					
+	            foreach($this -> _aProfileFields as $sKey => $aItem)
+					if (empty($aCurrentMembersValues[$sKey]))
+							$sUpdateField .= "`{$sKey}` = '". addslashes(isset($aValues[$sKey]) && $aValues[$sKey] ? $aValues[$sKey] : 
+																			  (isset($aFieldsValues[$aItem['id']]) ? $aFieldsValues[$aItem['id']] : '')) . "',";
+                
+				if ($sUpdateField){
+					$sUpdateField = trim($sUpdateField, ',');
+
+	                $sQuery = 
+	                "
+	                	UPDATE
+	                		`bx_persons_data`
+	                	SET
+	                		{$sUpdateField}
+	                	WHERE
+	                		`id` = {$iProfileId}
+	                ";
+					
+					$this -> _oDb -> query($sQuery);
+			}	
+		}
+	}            
+}
+
+/** @} */	

+ 92 - 0
modules/boonex/se_migration/classes/BxSEMigStudioPage.php

@@ -0,0 +1,92 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+define('BX_SEMIG_STUDIO_TRANSFER', 'config');
+
+class BxSEMigStudioPage extends BxTemplStudioModule
+{    
+	protected $_oModule;	
+	
+	function __construct($sModule = "", $sPage = "")
+    {
+		$this -> MODULE = 'bx_se_migration';
+		parent::__construct($sModule, $sPage);
+	
+		$this -> _oModule = BxDolModule::getInstance($sModule);			
+		
+		$this->aMenuItems = array(
+            array('name' => 'settings', 'icon' => 'cogs', 'title' => '_bx_se_migration_cpt_settings'),
+            array('name' => 'config', 'icon' => 'exchange', 'title' => '_bx_se_migration_cpt_transfer_data'),
+        );	
+		
+		$this -> _oModule -> _oTemplate -> addStudioJs(array('transfer.js', 'BxDolGrid.js'));
+		$this -> _oModule -> _oTemplate -> addStudioCss(array('main.css'));	
+     } 
+	
+	public function saveData($sPath){
+		$isDbDefined = false;
+        $isDirDefined = false;
+
+		if ( substr($sPath, strlen($sPath) - 1 ) != DIRECTORY_SEPARATOR ) {
+                $sPath .= DIRECTORY_SEPARATOR;
+        }    
+
+			   
+	   // get all settings from social engine config file;	   
+	  if (file_exists($sPath . 'application' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'database.php')) 
+		$aSEConfig = include $sPath . 'application' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'database.php';
+	  else 
+		return MsgBox( _t('_bx_se_migration_error_config_file_was_not_found'));
+		
+
+      if( is_array($aSEConfig['params']) ) {
+           foreach($aSEConfig['params'] as $sKey => $sParamName){
+                   $aConfig[$sKey] = $sParamName;
+                   $isDbDefined = true; 
+           }
+      }
+
+      // save social engine data and creates migration list
+      if($isDbDefined) {
+			$aConfig['root'] = $sPath;			
+			$this -> _oModule -> _oDb -> saveConfig($aConfig);
+			
+			if ($this -> _oModule -> initSEDb())
+				$this -> _oModule -> createMigration();
+			else 	
+				return MsgBox( _t('_bx_se_migration_error_data_was_not_set'), 2);
+       }
+       else
+			return MsgBox( _t('_bx_se_migration_error_data_was_not_set'), 2);
+              
+
+		return MsgBox( _t('_bx_se_migration_data_was_set'), 2);			
+	}
+	
+	
+	public function getConfig(){		
+		bx_import('BxSEMigTransfers');
+		
+		$sMessage = '';			
+		if (bx_get('save') && bx_get('se_migration_location')) 
+			$sMessage = $this -> saveData(bx_get('se_migration_location'));		
+			
+		
+		$oGrid = BxDolGrid::getObjectInstance('bx_se_migration_transfers'); 
+			if ($oGrid)	
+				return $sMessage . $oGrid -> getCode(); // print grid object 
+		
+        
+		return MsgBox(_t('_bx_se_migration_installation_problem'));
+	}
+}
+
+/** @} */

+ 21 - 0
modules/boonex/se_migration/classes/BxSEMigTemplate.php

@@ -0,0 +1,21 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Social Engine Migration
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxSEMigTemplate extends BxBaseModGeneralTemplate
+{
+    public function __construct(&$oConfig, &$oDb)
+    {
+        $this -> MODULE = 'bx_se_migration';
+        parent::__construct($oConfig, $oDb);
+    }
+}
+
+/** @} */

+ 73 - 0
modules/boonex/se_migration/classes/BxSEMigTransfers.php

@@ -0,0 +1,73 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Albums Albums
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxSEMigTransfers extends BxTemplGrid
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+    	$this -> MODULE = 'bx_se_migration';
+		$this -> _oModule = BxDolModule::getInstance('bx_se_migration');
+		$this -> _aConfirmMessages['run'] = _t('_bx_se_migration_start_transfer_confirmation');
+		
+        parent::__construct ($aOptions, $oTemplate);
+    }
+	
+	protected function _getCellStatusText($mixedValue, $sKey, $aField, $aRow) {        
+		$sAttr = $this->_convertAttrs($aField, 'attr_cell', false, isset($aField['width']) ? 'width:' . $aField['width'] : false);				
+		
+		if ($aRow['status'] != 'finished') $mixedValue = _t('_bx_se_migration_status_' . $aRow['status']);
+		
+		return '<td ' . $sAttr . '><span class="' . ($aRow['status'] == 'finished' ? 'bx-se-migrate-not-finished' : ($aRow['status'] == 'error' ? 'bx-se-migrate-error' : '')) . '">' . $mixedValue . '</span></td>';
+	} 
+	
+	protected function _getCellModule($mixedValue, $sKey, $aField, $aRow) {        
+		return '<td>' .  _t('_bx_se_migration_data_' . $mixedValue) . '</td>';
+	} 
+	
+	 public function performActionRun() {
+		$aElements = bx_get('ids');		
+		echoJson(array(
+			'msg' => $this-> _oModule -> actionStartTransfer($aElements), 
+			'grid' => $this -> getCode(false),
+			'blink' => $aElements,
+		));		   
+	 }
+	 
+	 protected function _getFilterControls() {
+        $aForm = array(
+            'form_attrs' => array(),
+            'inputs' => array (
+                'path' => array(
+                    'type' => 'text',
+                    'name' => 'se_migration_location',
+                    'caption' => $this -> _oModule -> _oDb -> isConfigInstalled() ? _t('_bx_se_migration_se_defined_path') : _t('_bx_se_migration_cpt_put_path'),
+                    'value' => $this -> _oModule -> _oDb -> getExtraParam('root'),
+					'attrs_wrap'
+                ),   
+				
+				'save' => array (
+                    'type' => 'submit',
+                    'name' => 'save',
+                    'value' => _t('_bx_se_migration_cpt_' . ($this -> _oModule -> _oDb -> getExtraParam('root') ? 'update' : 'save'))					
+                )
+            )
+        );
+        
+		
+		if ($this -> _oModule -> _oDb -> isConfigInstalled()) 
+				$aForm['inputs']['save']['attrs'] = array('onclick' => "javascript:return confirm('" . bx_js_string(_t('_bx_se_migration_reupload_data')) . "');");
+		
+		$oForm = new BxTemplStudioFormView($aForm);
+        return $oForm -> getCode();
+    }
+}
+
+/** @} */

+ 70 - 0
modules/boonex/se_migration/install/config.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Protean Protean template
+ * @ingroup     TridentModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_TEMPLATE,
+    'name' => 'bx_se_migration',
+    'title' => 'Social Engine Migration',
+    'note' => 'Migration Tool',
+    'version' => '9.0.3',
+    'vendor' => 'Boonex',
+	'help_url' => 'http://feed.boonex.com/?section={module_name}',
+
+    'compatible_with' => array(
+        '9.0.0-B3'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/se_migration/',
+    'home_uri' => 'se_migration',
+
+    'db_prefix' => 'bx_semig_',
+    'class_prefix' => 'BxSEMig',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Boonex Social Migration Tool',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'clear_db_cache' => 1
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+    	'clear_db_cache' => 1
+    ),
+    'enable' => array(
+        'execute_sql' => 1,    	
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array(
+        'execute_sql' => 1,    	
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+);
+
+/** @} */

+ 20 - 0
modules/boonex/se_migration/install/installer.php

@@ -0,0 +1,20 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Protean Protean template
+ * @ingroup     TridentModules
+ *
+ * @{
+ */
+
+class BxSEMigInstaller extends BxBaseModTemplateInstaller
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}
+
+/** @} */

+ 56 - 0
modules/boonex/se_migration/install/langs/en.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_se_migration_wgt_cpt"><![CDATA[SE Migration]]></string>
+    <string name="_bx_se_migration_stg_cpt_type"><![CDATA[SE Migration]]></string>
+    <string name="_bx_se_migration_option_version"><![CDATA[By default, only Social Engine 4 is available]]></string>
+    <string name="_bx_se_migration_cpt_settings"><![CDATA[Settings]]></string>
+    <string name="_bx_se_migration_cpt_transfer_data"><![CDATA[Data Transfer]]></string>
+    <string name="_bx_se_migration_cpt_put_path"><![CDATA[Put current Social Engine Script location]]></string>   
+    <string name="_bx_se_migration_cpt_save"><![CDATA[Save]]></string>
+    <string name="_bx_se_migration_cpt_update"><![CDATA[Update]]></string>
+    <string name="_bx_se_migration_error_data_was_not_set"><![CDATA[Configuration was not defined]]></string>
+    <string name="_bx_se_migration_error_config_file_was_not_found"><![CDATA[Sorry configuration file was not found, please check path to the Social Engine Script!]]></string>
+    <string name="_bx_se_migration_data_was_set"><![CDATA[Social Engine Configuration was successfully set]]></string>
+    <string name="_bx_se_migration_avaialble_modules_to_transfer"><![CDATA[Available content for transfer]]></string>
+    <string name="_bx_se_migration_no_data_to_transfer"><![CDATA[No data to transfer]]></string>   
+    <string name="_bx_se_migration_modules_name"><![CDATA[Data]]></string>   
+    <string name="_bx_se_migration_modules_records_number"><![CDATA[Records number]]></string>   
+    <string name="_bx_se_migration_modules_status"><![CDATA[Status]]></string>
+    <string name="_bx_se_migration_se_defined_path"><![CDATA[Defined path to social engine script]]></string>
+    <string name="_bx_se_migration_start_transfer"><![CDATA[Start transfer]]></string>
+    <string name="_bx_se_migration_start_transfer_confirmation"><![CDATA[Please don't reload or close the page during data transferring]]></string>   
+    <string name="_bx_se_migration_reupload_data"><![CDATA[If you update path to config file, all information from table below will be lost, but all transferred data will be saved.]]></string>   
+    <string name="_bx_se_migration_already_transfer"><![CDATA[Module {0} already transferred]]></string>
+    <string name="_bx_se_migration_install_before"><![CDATA[Please transfer {0} first!]]></string>
+    <string name="_bx_se_migration_install_plugin"><![CDATA[You need to install {0} plugin(s), before {1} transfer]]></string>
+    <string name="_bx_se_migration_successfully_finished"><![CDATA[Transfer was successfully finished]]></string>
+    <string name="_bx_se_migration_successfully_failed"><![CDATA[Transfer is failed]]></string>
+    <string name="_bx_se_migration_define_migration_method"><![CDATA[Please define migration method for this Data]]></string>
+    <string name="_bx_se_migration_started_migration_profiles"><![CDATA[Profile Migration was started]]></string>
+    <string name="_bx_se_migration_started_migration_profiles_finished"><![CDATA[{0} Profiles were transferred]]></string>
+    <string name="_bx_se_migration_started_migration_profile_fields"><![CDATA[Profile Fields Migration was started]]></string>
+    <string name="_bx_se_migration_started_migration_profile_fields_finished"><![CDATA[{0} Profile Fields were transferred]]></string>
+    <string name="_bx_se_migration_started_migration_profile_field_can_not_be_transferred"><![CDATA[Custom Fields can not be transferred]]></string>   
+    <string name="_bx_se_migration_started_migration_blogs"><![CDATA[Blogs Migration was started]]></string>
+    <string name="_bx_se_migration_started_migration_photos"><![CDATA[Photos Migration was started]]></string>
+    <string name="_bx_se_migration_started_migration_photos_finished"><![CDATA[{0} Photos were transferred]]></string>
+    <string name="_bx_se_migration_started_migration_photos_album_error"><![CDATA[Database Error. Albums can not be created!]]></string>   
+    <string name="_bx_se_migration_started_migration_videos"><![CDATA[Videos Migration was started]]></string>
+    <string name="_bx_se_migration_friends_exports_error"><![CDATA[Database error: Friends can not be transferred]]></string>
+    <string name="_bx_se_migration_all_data_transferred"><![CDATA[All Data was transferred]]></string>
+    <string name="_bx_se_migration_status_not_started"><![CDATA[Not started Yet]]></string>
+    <string name="_bx_se_migration_status_started"><![CDATA[Already started]]></string>   
+    <string name="_bx_se_migration_status_finished"><![CDATA[Finished]]></string>
+    <string name="_bx_se_migration_status_error"><![CDATA[Error occurred]]></string>
+    <string name="_bx_se_migration_data_profiles"><![CDATA[Profiles]]></string>
+    <string name="_bx_se_migration_data_profile_fields"><![CDATA[Profile Fields]]></string>   
+    <string name="_bx_se_migration_data_blogs"><![CDATA[Blogs]]></string>
+    <string name="_bx_se_migration_data_photos"><![CDATA[Photos]]></string>
+    <string name="_bx_se_migration_data_videos"><![CDATA[Videos]]></string>
+    <string name="_bx_se_migration_started_migration_blogs"><![CDATA[blogs Migration was started]]></string>
+    <string name="_bx_se_migration_started_migration_blogs_finished"><![CDATA[{0} blogs posts with {1} comments and {2} categories were transferred]]></string>
+    <string name="_bx_se_migration_started_migration_blogs_error"><![CDATA[Error occurred. Record Id is {0}]]></string>
+    <string name="_bx_se_migration_define_total_method"><![CDATA[Please define how to calculate total records number for this Data]]></string>
+    <string name="_bx_se_migration_salt"><![CDATA[Social Engine Secret]]></string>
+	<string name="_bx_se_migration_installation_problem"><![CDATA[Seems like module was installed not correct. Please reinstall it.]]></string>		
+</resources>

+ 15 - 0
modules/boonex/se_migration/install/sql/disable.sql

@@ -0,0 +1,15 @@
+SET @sName = 'bx_se_migration';
+
+-- SETTINGS
+DELETE FROM `top`, `toc`, `to` USING `sys_options_types` AS `top` LEFT JOIN `sys_options_categories` AS `toc` ON `top`.`id`=`toc`.`type_id` LEFT JOIN `sys_options` AS `to` ON `toc`.`id`=`to`.`category_id` WHERE `top`.`name`=@sName;
+DELETE FROM `sys_options` WHERE `name` = 'se_migration_salt';
+
+-- ALERTS
+SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = @sName LIMIT 1);
+DELETE FROM `sys_alerts` WHERE `handler_id` = @iHandler;
+DELETE FROM `sys_alerts_handlers` WHERE `id` = @iHandler;
+
+-- GRIDS
+DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_se_migration_transfers');
+DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_se_migration_transfers', 'bx_se_migration_transfers_path');
+DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_se_migration_transfers');

+ 37 - 0
modules/boonex/se_migration/install/sql/enable.sql

@@ -0,0 +1,37 @@
+SET @sName = 'bx_se_migration';
+
+-- SETTINGS
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types`(`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules',  @sName, '_bx_se_migration_wgt_cpt', 'bx_se_migration@modules/boonex/se_migration/|std-mi.png', IF(ISNULL(@iTypeOrder), 1, @iTypeOrder + 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
+VALUES (@iTypeId,  @sName, '_bx_se_migration_wgt_cpt', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('se_migration_version', 'engine4', @iCategId, '_bx_se_migration_option_version', 'select', '', '', 'engine4', 1),
+('se_migration_salt', '', 0, '_bx_se_migration_salt', 'digit', '', '', '', 0);
+
+-- ALERTS
+INSERT INTO `sys_alerts_handlers` (`name`, `class`, `file`, `service_call`) VALUES 
+(@sName, 'BxSEMigAlertsResponse', 'modules/boonex/se_migration/classes/BxSEMigAlertsResponse.php', '');
+SET @iHandler := LAST_INSERT_ID();
+
+INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
+('system', 'encrypt_password_after', @iHandler);
+
+-- GRIDS:
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `visible_for_levels`, `override_class_name`, `override_class_file`) VALUES
+('bx_se_migration_transfers', 'Sql', 'SELECT `id`, `module`, `number`, `status`, `status_text`, '''' as `datatype`  FROM `bx_semig_transfers`', 'bx_semig_transfers', 'module', 'id', '', '', 0, '', 'start', '', 'path', '', 'auto', 'module', '', 2147483647, 'BxSEMigTransfers', 'modules/boonex/se_migration/classes/BxSEMigTransfers.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_se_migration_transfers', 'checkbox', '', '1%', 0, 0, '', 1),
+('bx_se_migration_transfers', 'module', '_bx_se_migration_modules_name', '30%', 0, 0, '', 2),
+('bx_se_migration_transfers', 'number', '_bx_se_migration_modules_records_number', '10%', 0, 0, '', 3),
+('bx_se_migration_transfers', 'status_text', '_bx_se_migration_modules_status', '59%', 0, 0, '', 4),
+('bx_se_migration_transfers_path', 'title', '_bx_se_migration_modules_path', '100%', 0, 0, '', 0);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_se_migration_transfers', 'bulk', 'run', '_bx_se_migration_start_transfer', '', 0, 1, 1);

+ 33 - 0
modules/boonex/se_migration/install/sql/install.sql

@@ -0,0 +1,33 @@
+SET @sName = 'bx_se_migration';
+
+-- STUDIO: page & widget
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, @sName, '_bx_se_migration_wgt_cpt', '_bx_se_migration_wgt_cpt', 'bx_se_migration@modules/boonex/se_migration/|std-pi.png');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name` = 'home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id` = @iParentPageId);
+INSERT INTO `sys_std_widgets` (`page_id`, `module`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, @sName, '{url_studio}module.php?name=bx_se_migration', '', 'bx_se_migration@modules/boonex/se_migration/|std-wi.png', '_bx_se_migration_wgt_cpt', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+
+INSERT INTO `sys_std_pages_widgets` (`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(ISNULL(@iParentPageOrder), 1, @iParentPageOrder + 1));
+
+
+CREATE TABLE IF NOT EXISTS `bx_semig_transfers` (
+	`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+	`module` varchar(50) NOT NULL,
+	`number` int(10) unsigned NOT NULL,
+	`status` enum('not_started','started','finished','error') NOT NULL,
+	`status_text` varchar(255) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE  `module` (  `module` )
+);
+
+CREATE TABLE IF NOT EXISTS `bx_semig_config` (
+	`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+	`name` varchar(100) NOT NULL,
+	`value` varchar(150) NOT NULL,
+	PRIMARY KEY (`id`),
+	UNIQUE  `name` (  `name` )
+);

+ 8 - 0
modules/boonex/se_migration/install/sql/uninstall.sql

@@ -0,0 +1,8 @@
+SET @sName = 'bx_se_migration';
+
+-- STUDIO PAGE & WIDGET
+DELETE FROM `tp`, `tw`, `tpw`
+USING `sys_std_pages` AS `tp`, `sys_std_widgets` AS `tw`, `sys_std_pages_widgets` AS `tpw`
+WHERE `tp`.`id` = `tw`.`page_id` AND `tw`.`id` = `tpw`.`widget_id` AND `tp`.`name` = @sName;
+
+DROP TABLE IF EXISTS `bx_semig_config`, `bx_semig_transfers`;

+ 19 - 0
modules/boonex/se_migration/request.php

@@ -0,0 +1,19 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    Developer Developer
+ * @ingroup     TridentModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLanguages');
+bx_import('BxDolRequest');
+
+BxDolRequest::processAsAction($GLOBALS['aModule'], $GLOBALS['aRequest']);
+
+/** @} */

+ 16 - 0
modules/boonex/se_migration/template/css/main.css

@@ -0,0 +1,16 @@
+.bx-se-migrate-not-finished {
+	color:limegreen;
+	font-weight:bold;
+}
+
+.bx-se-migrate-error {
+	font-weight:bold;
+	color:red;
+}
+table.bx-grid-table td{
+	text-align:center;
+}
+
+.bx-grid-header-cnt > div{
+	width:50%;	
+}

+ 69 - 0
modules/boonex/se_migration/template/images/icons/std-icon.svg

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="600px" height="600px" viewBox="0 0 600 600" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 41.1 (35376) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 2 Copy 31</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
+            <stop stop-color="#FFFFFF" offset="0%"></stop>
+            <stop stop-color="#FFFFFF" offset="100%"></stop>
+        </linearGradient>
+        <path d="M552.268958,552.268958 C600,504.537813 600,431.458333 600,300 C600,168.541667 600,95.4621875 552.268958,47.7310417 C504.537813,0 431.458333,0 300,0 C168.541667,0 95.4621875,0 47.7310417,47.7310417 C0,95.4621875 0,168.541667 0,300 C0,431.458333 0,504.537813 47.7310417,552.268958 C95.4621875,600 168.541667,600 300,600 C431.458333,600 504.537813,600 552.268958,552.268958 L552.268958,552.268958 Z" id="path-2"></path>
+        <mask id="mask-3" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="600" height="600" fill="white">
+            <use xlink:href="#path-2"></use>
+        </mask>
+        <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-4">
+            <stop stop-color="#FFFFFF" stop-opacity="0.2" offset="0%"></stop>
+            <stop stop-color="#000000" stop-opacity="0.138870018" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Group-2-Copy-31">
+            <use id="path-copy-20" stroke="#6F6E6E" mask="url(#mask-3)" stroke-width="8" fill="url(#linearGradient-1)" xlink:href="#path-2"></use>
+            <g id="Group-2" transform="translate(59.000000, 217.000000)">
+                <g id="logo" transform="translate(0.000000, 0.789474)">
+                    <g id="Slice-1">
+                        <g id="Page-1">
+                            <g id="Logo">
+                                <g id="mark">
+                                    <polygon id="Fill-17" fill="#FFCF01" points="70.9046667 164.002794 106.650484 144.364198 36.3891284 103.06259 0.268399272 124.556769"></polygon>
+                                    <polygon id="Fill-18" fill="#40BEB4" points="71.1653723 123.587601 70.9046667 164.002239 142.222234 124.895467 142.222234 84.1384066"></polygon>
+                                    <polygon id="Fill-19" fill="#01A5DB" points="142.222786 84.1384066 142.222786 124.895467 71.1659237 83.9973155 71.5006666 42.4816928"></polygon>
+                                    <polygon id="Fill-20" fill="#EF3F2D" points="71.1653723 83.8429081 71.1653723 123.58776 0.191211495 81.7305068 0.157343389 42.4263661"></polygon>
+                                    <polygon id="Fill-21" fill="#F16625" points="0.157815967 42.4263661 0.157815967 81.7312994 71.4998003 42.6633673 71.1658449 0.733190697"></polygon>
+                                    <polygon id="Fill-22" fill="#FFCF01" points="107.082105 63.4398851 141.878828 41.9116219 71.1653723 0.733666285 35.5558154 21.6745789"></polygon>
+                                    <polygon id="Fill-23" fill="#00A1AF" points="142.222786 84.1384066 106.18082 104.168569 142.222786 124.895467"></polygon>
+                                    <polygon id="Fill-24" fill="#5DA73C" points="71.1653723 123.587601 70.9046667 164.002239 106.651271 144.364436"></polygon>
+                                    <polygon id="Fill-25" fill="#ED1F2E" points="0.157815967 42.4263661 0.190896443 81.7312994 35.4444444 62.974919"></polygon>
+                                    <polygon id="Fill-26" fill="#F0532D" points="71.1653723 0.733587021 71.5001154 42.6629709 35.5558154 21.6744997"></polygon>
+                                    <polygon id="Fill-27" fill="#FEBE10" points="71.1653723 123.587601 35.5825949 144.274075 70.9046667 164.002239"></polygon>
+                                    <polygon id="Fill-28" fill="#FEBE10" points="71.4996428 42.6629709 108.050418 22.2127062 71.1648999 0.733587021"></polygon>
+                                </g>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+                <g id="logosvg-animated-copy" transform="translate(317.378587, 0.000000)">
+                    <g id="Group">
+                        <g id="inner">
+                            <g id="XMLID_5_">
+                                <path d="M33.5078903,82.5110674 L33.5078903,82.5110674 C33.5078903,55.4002881 55.4238619,33.367113 82.4565788,33.367113 C102.063223,33.367113 119.088853,44.9730653 126.786612,61.6566218 L126.786612,13.2833751 C114.107951,4.98693264 98.7577146,0.181343005 82.3660169,0.181343005 C37.0398031,0.181343005 0.271685597,37.1299803 0.271685597,82.5110674 C0.271685597,109.485839 13.1314705,133.468452 33.5078903,148.519921 L33.5078903,82.5110674 Z" id="Shape" fill="#2AB191"></path>
+                                <path d="M131.314705,16.5022135 L131.314705,82.5110674 C131.314705,109.621847 109.398734,131.655022 82.3660169,131.655022 C62.759373,131.655022 45.7337422,120.049069 38.0359836,103.365513 L38.0359836,151.73876 C50.7146448,160.035202 66.0648811,164.840792 82.4565788,164.840792 C127.782793,164.840792 164.55091,127.93749 164.55091,82.5110674 C164.55091,55.5362954 151.691125,31.5536829 131.314705,16.5022135 Z" id="Shape" fill="#4990E2"></path>
+                            </g>
+                        </g>
+                        <g id="outer" transform="translate(0.000000, 0.453358)">
+                            <path d="M31.1985628,12.5580031 C27.5308072,5.75764042 14.4898985,-0.997386529 0,0.181343005 L0,56.4883461 C5.43371195,38.6713959 16.5728214,23.3479119 31.1985628,12.5580031 Z" id="Shape" fill="#4990E2"></path>
+                            <path d="M133.624033,151.557417 C137.291789,158.357779 150.242135,165.112806 164.822596,163.934077 L164.822596,107.627074 C159.298322,125.444024 148.249774,140.767508 133.624033,151.557417 Z" id="Shape" fill="#EE6666"></path>
+                        </g>
+                    </g>
+                </g>
+                <g id="arrow-right-c" opacity="0.100000001" transform="translate(178.103493, 42.080644)" fill="#000000">
+                    <g id="Layer_1">
+                        <path d="M73.1367816,2.83067165 L103.36183,34.4498763 C105.169908,36.1964609 106.073948,38.5754297 106.073948,41.1953066 C106.073948,43.8151836 105.169908,46.1640388 103.36183,47.940737 L73.1367816,79.6803957 C69.5507589,83.4445868 63.7046379,83.4445868 60.1186153,79.6803957 C56.5325926,75.9162047 56.5325926,69.833272 60.1186153,66.069081 L74.6435138,50.8918627 L9.25133578,50.8918627 C4.12844626,50.8918627 0,46.5856282 0,41.2555337 C0,35.9254392 4.12844626,31.6192047 9.22120114,31.6192047 L74.6133791,31.6192047 L60.0884806,16.4419864 C56.502458,12.6777954 56.502458,6.59486269 60.0884806,2.83067165 C63.7046379,-0.933519376 69.5206243,-0.933519376 73.1367816,2.83067165 Z" id="Shape"></path>
+                    </g>
+                </g>
+            </g>
+            <path d="M600,300 C600,168.541667 600,95.4621875 552.268958,47.7310417 C504.537813,0 431.458333,0 300,0 C168.541667,0 95.4621875,0 47.7310417,47.7310417 C0,95.4621875 0,168.541667 0,300 C0,431.458333 0,504.537813 47.7310417,552.268958 C95.4621875,600 168.541667,600 300,600 C431.458333,600 504.537813,600 552.268958,552.268958 C600,504.537813 600,431.458333 600,300 Z" id="path-copy" fill="url(#linearGradient-4)"></path>
+        </g>
+    </g>
+</svg>