AntonLV 10 лет назад
Родитель
Сommit
c7e62f3d98

+ 30 - 0
favorite.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    TridentCore Trident Core
+ * @{
+ */
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLanguages');
+
+check_logged();
+
+$sSys = isset($_REQUEST['sys']) ? bx_process_input($_REQUEST['sys']) : false;
+$iObjectId = isset($_REQUEST['object_id']) ? bx_process_input($_REQUEST['object_id'], BX_DATA_INT) : 0;
+$sAction = isset($_REQUEST['action']) && preg_match ('/^[A-Za-z_-]+$/', $_REQUEST['action']) ? bx_process_input($_REQUEST['action']) : '';
+
+$oFavorite = BxDolFavorite::getObjectInstance($sSys, $iObjectId, true);
+
+if ($oFavorite && $sSys && $iObjectId && $sAction) {
+    header('Content-Type: text/html; charset=utf-8');
+    $sMethod = 'action' . ucfirst($sAction);
+    if(method_exists($oFavorite, $sMethod))
+        echo $oFavorite->$sMethod();
+}
+
+/** @} */

+ 234 - 0
inc/classes/BxDolFavorite.php

@@ -0,0 +1,234 @@
+<?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    TridentCore Trident Core
+ * @{
+ */
+
+define('BX_DOL_FAVORITE_USAGE_BLOCK', 'block');
+define('BX_DOL_FAVORITE_USAGE_INLINE', 'inline');
+define('BX_DOL_FAVORITE_USAGE_DEFAULT', BX_DOL_FAVORITE_USAGE_BLOCK);
+
+/**
+ * @page objects
+ * @section views Favorites
+ * @ref BxDolFavorite
+ */
+
+/**
+ * Track any object favorites automatically
+ *
+ * Add record to sys_object_favorite table to track object favorites,
+ * to record favorites just create this class instance with your object id,
+ * for example:
+ * @code
+ *  BxDolFavorite::getObjectInstance('my_system', 25); // 25 - is object id
+ * @endcode
+ *
+ * Description of sys_object_favorite table fields:
+ * @code
+ *  `name` - system name, it is better to use unique module prefix here, lowercase and all spaces are underscored
+ *  `table_track` - table to track favorites
+ *  `is_on` - is the system activated
+ *  `trigger_table` - table where you need to update favorites field
+ *  `trigger_field_id` - table field id to unique determine object
+ *  `trigger_field_count` - table field where total favorites number is stored
+ *  `class_name` - your custom class name, if you overrride default class
+ *  `class_file` - your custom class path
+ * @endcode
+ *
+ * Structure of the track table is the following:
+ * @code
+ *  CREATE TABLE `my_favorites_track` (
+ *      `object_id` int(11) NOT NULL default '0', -- this field type must be exact as your object id type
+ *      `author_id` int(11) NOT NULL default '0', -- favoring user profile id
+ *      `author_nip` int(11) unsigned NOT NULL default '0', -- favoring user ip address
+ *      `date` int(11) NOT NULL default '0', -- timestamp of last recorded view
+ *      KEY `id` (`object_id`,`author_id`)
+ *  ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+ * @endcode
+ *
+ */
+class BxDolFavorite extends BxDolObject
+{
+    protected $_oTemplate;
+
+    protected $_sBaseUrl;
+
+    /**
+     * Constructor
+     */
+    function __construct($sSystem, $iId, $iInit = true)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate = false);
+        if(empty($this->_sSystem))
+            return;
+
+        $this->_oQuery = new BxDolFavoriteQuery($this);
+
+        if ($oTemplate)
+            $this->_oTemplate = $oTemplate;
+        else
+            $this->_oTemplate = BxDolTemplate::getInstance();
+
+        $this->_sBaseUrl = BxDolPermalinks::getInstance()->permalink($this->_aSystem['base_url']);
+        if(get_mb_substr($this->_sBaseUrl, 0, 4) != 'http')
+            $this->_sBaseUrl = BX_DOL_URL_ROOT . $this->_sBaseUrl;
+    }
+
+   /**
+     * get favorites object instanse
+     * @param $sSys favorite object name
+     * @param $iId associated content id, where favorite is available
+     * @param $iInit perform initialization
+     * @return null on error, or ready to use class instance
+     */
+    public static function getObjectInstance($sSys, $iId, $iInit = true)
+    {
+        if(isset($GLOBALS['bxDolClasses']['BxDolFavorite!' . $sSys . $iId]))
+            return $GLOBALS['bxDolClasses']['BxDolFavorite!' . $sSys . $iId];
+
+        $aSystems = self::getSystems();
+        if(!isset($aSystems[$sSys]))
+            return null;
+
+        $sClassName = 'BxTemplFavorite';
+        if(!empty($aSystems[$sSys]['class_name'])) {
+            $sClassName = $aSystems[$sSys]['class_name'];
+            if(!empty($aSystems[$sSys]['class_file']))
+                require_once(BX_DIRECTORY_PATH_ROOT . $aSystems[$sSys]['class_file']);
+        }
+
+        $o = new $sClassName($sSys, $iId, $iInit);
+        return ($GLOBALS['bxDolClasses']['BxDolFavorite!' . $sSys . $iId] = $o);
+    }
+
+    public static function &getSystems()
+    {
+        if(!isset($GLOBALS['bx_dol_favorite_systems']))
+            $GLOBALS['bx_dol_favorite_systems'] = BxDolDb::getInstance()->fromCache('sys_objects_favorite', 'getAllWithKey', '
+                SELECT
+                    `id` as `id`,
+                    `name` AS `name`,
+                    `table_main` AS `table_main`,
+                    `table_track` AS `table_track`,
+                    `is_on` AS `is_on`,
+                    `base_url` AS `base_url`,
+                    `trigger_table` AS `trigger_table`,
+                    `trigger_field_id` AS `trigger_field_id`,
+                    `trigger_field_author` AS `trigger_field_author`,
+                    `trigger_field_count` AS `trigger_field_count`,
+                    `class_name` AS `class_name`,
+                    `class_file` AS `class_file`
+                FROM `sys_objects_favorite`', 'name');
+
+        return $GLOBALS['bx_dol_favorite_systems'];
+    }
+
+	/**
+     * Actions functions
+     */
+    public function actionFavorite()
+    {
+        return echoJson($this->_doFavorite());
+    }
+
+	public function actionGetFavoritedBy()
+    {
+        if (!$this->isEnabled())
+           return '';
+
+	    if(!$this->isAllowedFavoriteView(true))
+            return $this->msgErrAllowedFavoriteView();
+
+        return $this->_getFavoritedBy();
+    }
+
+    /**
+     * Permissions functions
+     */
+    public function isAllowedFavorite($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('favorite', $isPerformAction);
+    }
+
+	public function msgErrAllowedFavorite()
+    {
+        return $this->checkActionErrorMsg('favorite');
+    }
+
+    public function isAllowedFavoriteView($isPerformAction = false)
+    {
+        if(isAdmin())
+            return true;
+
+        return $this->checkAction('favorite_view', $isPerformAction);
+    }
+
+    public function msgErrAllowedFavoriteView()
+    {
+        return $this->checkActionErrorMsg('favorite_view');
+    }
+
+	/**
+     * Internal functions
+     */
+	protected function _doFavorite()
+    {
+        if (!$this->isEnabled())
+           return array('code' => 1, 'msg' => _t('_favorite_err_not_enabled'));
+
+	    if(!$this->isAllowedFavorite())
+            return array('code' => 2, 'msg' => $this->msgErrAllowedFavorite());
+
+        $iObjectId = $this->getId();
+        $iObjectAuthorId = $this->_oQuery->getObjectAuthorId($iObjectId);
+        $iAuthorId = $this->_getAuthorId();
+        $sAuthorIp = $this->_getAuthorIp();
+
+        $bPerformed = $this->_oQuery->isPerformed($iObjectId, $iAuthorId);
+        if($bPerformed)
+        	return array('code' => 3, 'msg' => _t('_favorite_err_duplicate_favorite'));
+
+        $iId = (int)$this->_oQuery->doFavorite($iObjectId, $iAuthorId, $sAuthorIp);
+        if($iId == 0)
+            return array('code' => 4, 'msg' => _t('_favorite_err_cannot_perform_action'));
+
+        if(!$this->isAllowedFavorite(true))
+            return array('code' => 2, 'msg' => $this->msgErrAllowedFavorite());
+
+        $this->_trigger();
+
+        bx_alert($this->_sSystem, 'favorite', $iObjectId, $iAuthorId, array('favorite_id' => $iId, 'favorite_author_id' => $iAuthorId, 'object_author_id' => $iObjectAuthorId));
+        bx_alert('favorite', 'do', $iId, $iAuthorId, array('object_system' => $this->_sSystem, 'object_id' => $iObjectId, 'object_author_id' => $iObjectAuthorId));
+
+        $aFavorite = $this->_oQuery->getFavorite($iObjectId);
+        return array(
+        	'eval' => $this->getJsObjectName() . '.onFavorite(oData, oElement)',
+        	'code' => 0, 
+        	'id' => $iId,
+        	'count' => $aFavorite['count'],
+        	'countf' => (int)$aFavorite['count'] > 0 ? $this->_getLabelCounter($aFavorite['count']) : '',
+            'label_icon' => $this->_getIconDoFavorite(!$bPerformed),
+            'label_title' => _t($this->_getTitleDoFavorite(!$bPerformed)),
+            'disabled' => !$bPerformed
+        );
+    }
+
+    protected function _getIconDoFavorite($bPerformed)
+    {
+    	return 'star';
+    }
+
+    protected function _getTitleDoFavorite($bPerformed)
+    {
+    	return '_favorite_do_favorite';
+    }
+}
+
+/** @} */

+ 87 - 0
inc/classes/BxDolFavoriteQuery.php

@@ -0,0 +1,87 @@
+<?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    TridentCore Trident Core
+ * @{
+ */
+
+/**
+ * @see BxDolFavorite
+ */
+class BxDolFavoriteQuery extends BxDolObjectQuery
+{
+    public function __construct(&$oModule)
+    {
+        parent::__construct($oModule);
+
+        $this->_sMethodGetEntry = 'getFavorite';
+    }
+
+    public function isPerformed($iObjectId, $iAuthorId)
+    {
+        $sQuery = $this->prepare("SELECT `date` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        return (int)$this->getOne($sQuery) > 0;
+    }
+
+	public function getPerformedBy($iObjectId)
+    {
+        $sQuery = $this->prepare("SELECT `author_id` FROM `{$this->_sTableTrack}` WHERE `object_id`=? ORDER BY `date` DESC", $iObjectId);
+        return $this->getAll($sQuery);
+    }
+
+	public function getFavorite($iObjectId)
+    {
+        $sQuery = $this->prepare("SELECT `count` as `count` FROM {$this->_sTable} WHERE `object_id` = ? LIMIT 1", $iObjectId);
+
+        $aResult = $this->getRow($sQuery);
+        if(empty($aResult) || !is_array($aResult))
+            $aResult = array('count' => 0);
+
+        return $aResult;
+    }
+
+    public function doFavorite($iObjectId, $iAuthorId, $sAuthorIp)
+    {
+        $iAuthorNip = ip2long($sAuthorIp);
+
+        $sQuery = $this->prepare("SELECT `date` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        if((int)$this->getOne($sQuery) > 0)
+        	return true;
+
+        $sQuery = $this->prepare("INSERT IGNORE INTO `{$this->_sTableTrack}` SET `object_id` = ?, `author_id` = ?, `author_nip` = ?, `date` = ?", $iObjectId, $iAuthorId, $iAuthorNip, time());
+        if((int)$this->query($sQuery) == 0)
+            return false;
+
+        $sQuery = $this->prepare("SELECT `object_id` FROM `{$this->_sTable}` WHERE `object_id` = ? LIMIT 1", $iObjectId);
+        if((int)$this->getOne($sQuery) == 0)
+            $sQuery = $this->prepare("INSERT INTO `{$this->_sTable}` SET `object_id` = ?, `count` = '1'", $iObjectId);
+        else
+            $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count` = `count` + 1 WHERE `object_id` = ?", $iObjectId);
+
+        return (int)$this->query($sQuery) > 0;
+    }
+
+	public function undoFavorite($iObjectId, $iAuthorId)
+    {
+        $sQuery = $this->prepare("SELECT `date` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+        if((int)$this->getOne($sQuery) == 0)
+        	return true;
+
+        $sQuery = $this->prepare("DELETE FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);
+		if((int)$this->query($sQuery) == 0)
+		    return false;
+
+        $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count` = `count` - 1 WHERE `object_id` = ?", $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+
+    public function updateTriggerTable($iObjectId)
+    {
+        $sQuery = $this->prepare("UPDATE `{$this->_sTriggerTable}` SET `{$this->_sTriggerFieldCount}` = `{$this->_sTriggerFieldCount}` + 1 WHERE `{$this->_sTriggerFieldId}` = ?", $iObjectId);
+        return (int)$this->query($sQuery) > 0;
+    }
+}
+
+/** @} */

+ 11 - 0
inc/classes/BxDolObjectQuery.php

@@ -49,6 +49,17 @@ class BxDolObjectQuery extends BxDolDb
         );
     }
 
+    public function getSqlPartsTrack($sMainTable, $sMainField)
+    {
+        if(empty($sMainTable) || empty($sMainField))
+            return array();
+
+        return array (
+            'fields' => ", `{$this->_sTableTrack}`.`author_id` as `favorer_id` ",
+            'join' => " LEFT JOIN `{$this->_sTableTrack}` ON (`{$this->_sTableTrack}`.`object_id` = `{$sMainTable}`.`{$sMainField}`) ",
+        );
+    }
+
 	public function isPerformed($iObjectId, $iAuthorId)
     {
         $sQuery = $this->prepare("SELECT `object_id` FROM `{$this->_sTableTrack}` WHERE `object_id` = ? AND `author_id` = ? LIMIT 1", $iObjectId, $iAuthorId);

+ 1 - 1
inc/classes/BxDolReportQuery.php

@@ -60,7 +60,7 @@ class BxDolReportQuery extends BxDolObjectQuery
         $bExists = (int)$this->getOne($sQuery) != 0;
 
         if(!$bExists)
-            $sQuery = $this->prepare("INSERT INTO {$this->_sTable} SET `object_id` = ?, `count` = '1'", $iObjectId);
+            $sQuery = $this->prepare("INSERT INTO `{$this->_sTable}` SET `object_id` = ?, `count` = '1'", $iObjectId);
         else
             $sQuery = $this->prepare("UPDATE `{$this->_sTable}` SET `count` = `count` + 1 WHERE `object_id` = ?", $iObjectId);
 

+ 143 - 0
inc/js/classes/BxDolFavorite.js

@@ -0,0 +1,143 @@
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    TridentCore Trident Core
+ * @{
+ */
+
+function BxDolFavorite(options)
+{
+	this._sObjName = undefined == options.sObjName ? 'oFavorite' : options.sObjName; // javascript object name, to run current object instance from onTimer
+	this._sSystem = options.sSystem; // current comment system
+	this._iAuthorId = options.iAuthorId; // this comment's author ID.
+    this._iObjId = options.iObjId; // this object id comments
+
+    this._sActionsUri = 'favorite.php';
+    this._sActionsUrl = options.sRootUrl + this._sActionsUri; // actions url address
+
+    this._sAnimationEffect = 'fade';
+    this._iAnimationSpeed = 'slow';
+    this._sSP = undefined == options.sStylePrefix ? 'bx-favorite' : options.sStylePrefix;
+    this._aHtmlIds = options.aHtmlIds;
+
+    this._oParent = null;
+}
+
+BxDolFavorite.prototype.toggleByPopup = function(oLink) {
+	var $this = this;
+    var oData = this._getDefaultParams();
+    oData['action'] = 'GetFavoritedBy';
+
+	$(oLink).dolPopupAjax({
+		id: this._aHtmlIds['by_popup'], 
+		url: bx_append_url_params(this._sActionsUri, oData)
+	});
+};
+
+BxDolFavorite.prototype.favorite = function(oLink) {
+	var $this = this;
+    var oData = this._getDefaultParams();
+    oData['action'] = 'Favorite';
+
+    this._oParent = oLink;
+
+    $.get(
+    	this._sActionsUrl,
+    	oData,
+    	function(oData) {
+    		$this.processJson(oData, oLink);
+    	},
+    	'json'
+    );
+};
+
+BxDolFavorite.prototype.onFavorite = function(oData, oElement)
+{
+	console.log(oData);
+	if(oData && oData.msg != undefined && oData.msg.length > 0)
+        alert(oData.msg);
+
+	if(oData && oData.code != 0)
+        return;
+
+	if(oData && oData.label_icon)
+		$(oElement).find('.sys-icon').attr('class', 'sys-icon ' + oData.label_icon);
+
+	if(oData && oData.label_title) {
+		$(oElement).attr('title', oData.label_title);
+		$(oElement).find('span').html(oData.label_title);
+	}
+
+	if(oData && oData.disabled)
+		$(oElement).removeAttr('onclick').addClass($(oElement).hasClass('bx-btn') ? 'bx-btn-disabled' : 'bx-favorite-disabled');
+
+    var oCounter = this._getCounter(oElement);
+    console.log(oCounter, oData.countf);
+    if(oCounter && oCounter.length > 0) {
+    	oCounter.html(oData.countf);
+
+    	oCounter.parents('.' + this._sSP + '-counter-holder:first').bx_anim(oData.count > 0 ? 'show' : 'hide');
+    }
+};
+
+BxDolFavorite.prototype.processJson = function(oData, oElement) {
+	oElement = oElement != undefined ? oElement : this._oParent;
+
+    //--- Show Message
+    if(oData && oData.msg != undefined)
+        alert(oData.msg);
+    if(oData && oData.message != undefined)
+        alert(oData.message);
+
+    //--- Show Popup
+    if(oData && oData.popup != undefined) {
+    	$('#' + oData.popup_id).remove();
+
+    	$(oData.popup).hide().prependTo('body').dolPopup({
+    		pointer: {
+    			el: oElement
+    		},
+            fog: {
+				color: '#fff',
+				opacity: .7
+            }
+        });
+    }
+
+    //--- Evaluate JS code
+    if (oData && oData.eval != undefined)
+        eval(oData.eval);
+};
+
+BxDolFavorite.prototype._getButtons = function(oElement) {
+	if($(oElement).hasClass(this._sSP))
+		return $(oElement).find('.' + this._sSP + '-button');
+	else
+		return $(oElement).parents('.' + this._sSP + ':first').find('.' + this._sSP + '-button');
+};
+
+BxDolFavorite.prototype._getCounter = function(oElement) {
+	if($(oElement).hasClass(this._sSP))
+		return $(oElement).find('.' + this._sSP + '-counter');
+	else 
+		return $(oElement).parents('.' + this._sSP + ':first').find('.' + this._sSP + '-counter');
+};
+
+BxDolFavorite.prototype._loadingInButton = function(e, bShow) {
+	if($(e).length)
+		bx_loading_btn($(e), bShow);
+	else
+		bx_loading($('body'), bShow);	
+};
+
+BxDolFavorite.prototype._getDefaultParams = function() {
+	var oDate = new Date();
+    return {
+        sys: this._sSystem,
+        object_id: this._iObjId,
+        _t: oDate.getTime()
+    };
+};
+
+/** @} */

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
install/sql/system.sql


+ 8 - 0
modules/boonex/english/data/langs/system/en.xml

@@ -32,6 +32,12 @@
 	<string name="_Empty"><![CDATA[Empty]]></string>
 	<string name="_Error"><![CDATA[Error]]></string>
 	<string name="_Failed"><![CDATA[Failed]]></string>
+	<string name="_favorite_counter"><![CDATA[{0}]]></string>
+	<string name="_favorite_do_favorite"><![CDATA[Favorite]]></string>
+	<string name="_favorite_do_favorite_by"><![CDATA[Favorited by]]></string>
+	<string name="_favorite_err_cannot_perform_action"><![CDATA[Cannot perform the action.]]></string>
+	<string name="_favorite_err_duplicate_favorite"><![CDATA[You already favorited this item.]]></string>
+	<string name="_favorite_err_not_enabled"><![CDATA[The feature is not available right now. Please, try again later.]]></string>
 	<string name="_Female"><![CDATA[Woman]]></string>
 	<string name="_From"><![CDATA[From]]></string>
 	<string name="_Header"><![CDATA[Header]]></string>
@@ -1465,6 +1471,8 @@
 	<string name="_sys_acl_action_delete_account"><![CDATA[Delete Account]]></string>
 	<string name="_sys_acl_action_delete_account_desc"><![CDATA[Delete account also deletes account's content.]]></string>
 	<string name="_sys_acl_action_every_period"><![CDATA[ every {4} hours]]></string>
+	<string name="_sys_acl_action_favorite"><![CDATA[Favorite]]></string>
+	<string name="_sys_acl_action_favorite_view"><![CDATA[View favorites]]></string>
 	<string name="_sys_acl_action_limit_reached"><![CDATA[You have reached your limit for now. Your current membership ({2}) allows you to {1} no more than {3} times]]></string>
 	<string name="_sys_acl_action_not_allowed"><![CDATA[Your current membership ({2}) doesn't allow you to '{1}'.]]></string>
 	<string name="_sys_acl_action_not_allowed_after"><![CDATA[Your current membership ({2}) stopped to allow you to '{1}' {5}.]]></string>

+ 21 - 0
modules/boonex/ocean/data/template/system/scripts/BxTemplFavorite.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    TridentCore Trident Core
+ * @{
+ */
+
+/**
+ * @see BxDolFavorite
+ */
+class BxTemplFavorite extends BxBaseFavorite
+{
+    function __construct($sSystem, $iId, $iInit = 1)
+    {
+        parent::__construct($sSystem, $iId, $iInit);
+    }
+}
+
+/** @} */

+ 21 - 0
modules/boonex/protean/data/template/system/scripts/BxTemplFavorite.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    TridentCore Trident Core
+ * @{
+ */
+
+/**
+ * @see BxDolFavorite
+ */
+class BxTemplFavorite extends BxBaseFavorite
+{
+    function __construct($sSystem, $iId, $iInit = 1)
+    {
+        parent::__construct($sSystem, $iId, $iInit);
+    }
+}
+
+/** @} */

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

@@ -32,6 +32,12 @@
 	<string name="_Empty"><![CDATA[Пусто]]></string>
 	<string name="_Error"><![CDATA[Ошибка]]></string>
 	<string name="_Failed"><![CDATA[Неудача]]></string>
+	<string name="_favorite_counter"><![CDATA[{0}]]></string>
+	<string name="_favorite_do_favorite"><![CDATA[Фаворит]]></string>
+	<string name="_favorite_do_favorite_by"><![CDATA[Добавили в фавориты]]></string>
+	<string name="_favorite_err_cannot_perform_action"><![CDATA[Невозможно выполнить действие.]]></string>
+	<string name="_favorite_err_duplicate_favorite"><![CDATA[Вы уже добавляли данный объект в список фаворитов ранее.]]></string>
+	<string name="_favorite_err_not_enabled"><![CDATA[Действие не доступно в данный момент. Пожалуйста, попробуйте позднее.]]></string>
 	<string name="_Female"><![CDATA[Женщина]]></string>
 	<string name="_From"><![CDATA[От]]></string>
 	<string name="_Header"><![CDATA[Заголовок]]></string>
@@ -1463,6 +1469,8 @@
 	<string name="_sys_acl_action_delete_account"><![CDATA[Удалить Аккаунт]]></string>
 	<string name="_sys_acl_action_delete_account_desc"><![CDATA[Удалить аккаунт, также удалить содержимое аккаунта.]]></string>
 	<string name="_sys_acl_action_every_period"><![CDATA[ каждые {4} часа]]></string>
+	<string name="_sys_acl_action_favorite"><![CDATA[Фавориты - Добавлять]]></string>
+	<string name="_sys_acl_action_favorite_view"><![CDATA[Фавориты - Просматривать]]></string>
 	<string name="_sys_acl_action_limit_reached"><![CDATA[Вы достигли лимита. Ваше текущее членство ({2}) позволяет {1} не более {3} раз]]></string>
 	<string name="_sys_acl_action_not_allowed"><![CDATA[Ваше текущее членство ({2}) не позволяет '{1}'.]]></string>
 	<string name="_sys_acl_action_not_allowed_after"><![CDATA[Ваше текущее членство ({2}) более не позволяет '{1}' {5}.]]></string>

+ 67 - 0
template/css/favorite.css

@@ -0,0 +1,67 @@
+/*--- Do Form ---*/
+div.bx-favorite-do-form {}
+div.bx-favorite-do-form-cnt {
+	min-width: 220px;
+	min-width: 13.75rem;
+	overflow: hidden;
+}
+
+/*--- Favorited By List ---*/
+div.bx-favorite-by-list {}
+div.bx-favorite-by-list-cnt {
+	max-height: 400px;
+	max-height: 25rem;
+	min-width: 220px;
+	min-width: 13.75rem;
+	max-width: 320px;
+	max-width: 20rem;
+	overflow: auto;
+}
+div.bx-favorite-by-list div.bx-def-unit {
+	float: none;
+	margin-right: 0px;
+	margin-bottom: 0px;
+}
+div.bx-favorite-bi-thumbnail,
+div.bx-favorite-bi-info {
+	position: relative;
+}
+div.bx-favorite-bi-info p {
+	margin: 0px; 
+}
+
+/*--- Favorite Element Styles ---*/
+div.bx-favorite div.bx-favorite-element-holder,
+div.bx-favorite div.bx-favorite-counter-holder {
+	position: relative;
+	float: left;
+}
+
+/*--- Favorite Element Styles ---*/
+.bx-favorite-favorite .bx-favorite-element-holder,
+.bx-favorite-favorite .bx-favorite-counter-holder {
+	line-height: 20px;
+	line-height: 1.25rem;
+}
+
+.bx-favorite-button .bx-favorite-element-holder,
+.bx-favorite-button .bx-favorite-counter-holder {
+	line-height: 36px;
+	line-height: 2.2rem;
+}
+
+.bx-favorite-button-small .bx-favorite-element-holder,
+.bx-favorite-button-small .bx-favorite-counter-holder {
+	line-height: 24px;
+	line-height: 1.6rem;
+}
+
+.bx-favorite .bx-favorite-element-holder .bx-favorite-do-favorite.bx-favorite-disabled,
+.bx-favorite .bx-favorite-element-holder .bx-favorite-do-favorite.bx-favorite-disabled .sys-icon {
+	color: #ddd;
+}
+
+.bx-favorite-button .bx-favorite-counter-holder,
+.bx-favorite-button-small .bx-favorite-counter-holder {
+	font-size: 1.5rem;
+}

+ 11 - 0
template/favorite_by_list.html

@@ -0,0 +1,11 @@
+<div class="__style_prefix__-by-list">
+	<div class="__style_prefix__-by-list-cnt">
+		<div class="__style_prefix__-by-items">
+			<bx_repeat:list>
+				<div class="__style_prefix__-by-item bx-def-padding-sec bx-clearfix">
+					<div class="__style_prefix__-bi-thumbnail">__user_unit__</div>
+				</div>
+			</bx_repeat:list>
+		</div>
+	</div>
+</div>

+ 1 - 0
template/favorite_counter.html

@@ -0,0 +1 @@
+<bx_include_auto:bx_a.html />

+ 1 - 0
template/favorite_do_favorite.html

@@ -0,0 +1 @@
+<a id="__html_id__" class="__style_prefix__-do-favorite __class__" href="javascript:void(0)" <bx_if:show_onclick>onclick="javascript:__js_object__.favorite(this)"</bx_if:show_onclick> title="__title__">__do_favorite__</a>

+ 1 - 0
template/favorite_do_favorite_label.html

@@ -0,0 +1 @@
+<bx_if:show_icon><i class="sys-icon __name__"></i></bx_if:show_icon><bx_if:show_text><span class="bx-def-margin-sec-left-auto">__text__</span></bx_if:show_text>

+ 7 - 0
template/favorite_element_block.html

@@ -0,0 +1,7 @@
+<div id="__html_id__" class="__style_prefix__ __class__ bx-def-margin-right-auto bx-def-margin-thd-topbottom bx-clearfix" bx_favorite_data_count="__count__">
+	<div class="__style_prefix__-element-holder">__do_favorite__</div>
+	<bx_if:show_counter>
+		<div class="__style_prefix__-counter-holder bx-def-margin-sec-left"<bx_if:show_hidden> style="display:none"</bx_if:show_hidden>>__counter__</div>
+	</bx_if:show_counter>
+</div>
+__script__

+ 4 - 0
template/favorite_element_inline.html

@@ -0,0 +1,4 @@
+<span id="__html_id__" class="__style_prefix__ __class__ bx-def-margin-right-auto bx-def-margin-thd-topbottom" bx_favorite_data_count="__count__">
+	<span class="__style_prefix__-element-holder">__do_favorite__</span><bx_if:show_counter><span class="__style_prefix__-counter-holder bx-def-margin-thd-left"<bx_if:show_hidden> style="display:none"</bx_if:show_hidden>>__counter__</span></bx_if:show_counter>
+</span>
+__script__

+ 255 - 0
template/scripts/BxBaseFavorite.php

@@ -0,0 +1,255 @@
+<?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    TridentCore Trident Core
+ * @{
+ */
+
+/**
+ * @see BxDolFavorite
+ */
+class BxBaseFavorite extends BxDolFavorite
+{
+    protected $_bCssJsAdded;
+
+	protected $_sJsObjClass;
+    protected $_sJsObjName;
+    protected $_sStylePrefix;
+
+    protected $_aHtmlIds;
+
+    protected $_aElementDefaults;
+
+    protected $_sTmplNameCounter;
+    protected $_sTmplNameDoFavorite;
+
+    public function __construct($sSystem, $iId, $iInit = 1, $oTemplate = false)
+    {
+        parent::__construct($sSystem, $iId, $iInit, $oTemplate);
+
+        $this->_bCssJsAdded = false;
+
+        $this->_sJsObjClass = 'BxDolFavorite';
+        $this->_sJsObjName = 'oFavorite' . bx_gen_method_name($sSystem, array('_' , '-')) . $iId;
+        $this->_sStylePrefix = 'bx-favorite';
+
+        $sHtmlId = str_replace(array('_' , ' '), array('-', '-'), $sSystem) . '-' . $iId;
+        $this->_aHtmlIds = array(
+            'main' => 'bx-favorite-' . $sHtmlId,
+            'counter' => 'bx-favorite-counter-' . $sHtmlId,
+        	'do_link' => 'bx-favorite-do-link-' . $sHtmlId,
+            'by_popup' => 'bx-favorite-by-popup-' . $sHtmlId
+        );
+
+        $this->_aElementDefaults = array(
+			'show_do_favorite_as_button' => false,
+			'show_do_favorite_as_button_small' => false,
+			'show_do_favorite_icon' => true,
+			'show_do_favorite_label' => false,
+			'show_counter' => true
+        );
+
+        $this->_sTmplNameCounter = 'favorite_counter.html';
+        $this->_sTmplNameDoFavorite = 'favorite_do_favorite.html';
+    }
+
+    public function addCssJs($bDynamicMode = false)
+    {
+    	if($bDynamicMode || $this->_bCssJsAdded)
+    		return;
+
+    	$this->_oTemplate->addJs(array('BxDolFavorite.js'));
+        $this->_oTemplate->addCss(array('favorite.css'));
+
+        $this->_bCssJsAdded = true;
+    }
+
+    public function getJsObjectName()
+    {
+        return $this->_sJsObjName;
+    }
+
+    public function getJsScript($bDynamicMode = false)
+    {
+        $aParams = array(
+            'sObjName' => $this->_sJsObjName,
+            'sSystem' => $this->getSystemName(),
+            'iAuthorId' => $this->_getAuthorId(),
+            'iObjId' => $this->getId(),
+            'sRootUrl' => BX_DOL_URL_ROOT,
+            'sStylePrefix' => $this->_sStylePrefix,
+            'aHtmlIds' => $this->_aHtmlIds
+        );
+        $sCode = $this->_sJsObjName . " = new " . $this->_sJsObjClass . "(" . json_encode($aParams) . ");";
+
+        if($bDynamicMode) {
+			$sCode = "var " . $this->_sJsObjName . " = null; 
+			$.getScript('" . bx_js_string($this->_oTemplate->getJsUrl('BxDolFavorite.js'), BX_ESCAPE_STR_APOS) . "', function(data, textStatus, jqxhr) {
+				bx_get_style('" . bx_js_string($this->_oTemplate->getCssUrl('favorite.css'), BX_ESCAPE_STR_APOS) . "');
+				" . $sCode . "
+        	}); ";
+        }
+        else
+        	$sCode = "var " . $sCode;
+
+        $this->addCssJs($bDynamicMode);
+        return $this->_oTemplate->_wrapInTagJsCode($sCode);
+    }
+
+    public function getJsClick()
+    {
+        return $this->getJsObjectName() . '.favorite(this)';
+    }
+
+    public function getCounter($aParams = array())
+    {
+        $aFavorite = $this->_oQuery->getFavorite($this->getId());
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameCounter, array(
+            'href' => 'javascript:void(0)',
+            'title' => _t('_favorite_do_favorite_by'),
+            'bx_repeat:attrs' => array(
+                array('key' => 'id', 'value' => $this->_aHtmlIds['counter']),
+                array('key' => 'class', 'value' => $this->_sStylePrefix . '-counter'),
+                array('key' => 'onclick', 'value' => 'javascript:' . $this->getJsObjectName() . '.toggleByPopup(this)')
+            ),
+            'content' => (int)$aFavorite['count'] > 0 ? $this->_getLabelCounter($aFavorite['count']) : ''
+        ));
+    }
+
+    public function getElementBlock($aParams = array())
+    {
+        $aParams['usage'] = BX_DOL_FAVORITE_USAGE_BLOCK;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElementInline($aParams = array())
+    {
+        $aParams['usage'] = BX_DOL_FAVORITE_USAGE_INLINE;
+
+        return $this->getElement($aParams);
+    }
+
+    public function getElement($aParams = array())
+    {
+    	$aParams = array_merge($this->_aElementDefaults, $aParams);
+    	$bDynamicMode = isset($aParams['dynamic_mode']) && $aParams['dynamic_mode'] === true;
+
+        $bShowDoFavoriteAsButtonSmall = isset($aParams['show_do_favorite_as_button_small']) && $aParams['show_do_favorite_as_button_small'] == true;
+        $bShowDoFavoriteAsButton = !$bShowDoFavoriteAsButtonSmall && isset($aParams['show_do_favorite_as_button']) && $aParams['show_do_favorite_as_button'] == true;
+		$bShowCounter = isset($aParams['show_counter']) && $aParams['show_counter'] === true && $this->isAllowedFavoriteView();
+
+		$iObjectId = $this->getId();
+		$iAuthorId = $this->_getAuthorId();
+        $aFavorite = $this->_oQuery->getFavorite($iObjectId);
+
+        if(!$this->isAllowedFavorite() && (!$this->isAllowedFavoriteView() || (int)$aFavorite['count'] == 0))
+            return '';
+
+        $aParams['is_favorited'] = $this->_oQuery->isPerformed($iObjectId, $iAuthorId) ? true : false;
+
+        $sTmplName = 'favorite_element_' . (!empty($aParams['usage']) ? $aParams['usage'] : BX_DOL_FAVORITE_USAGE_DEFAULT) . '.html';
+        return $this->_oTemplate->parseHtmlByName($sTmplName, array(
+            'style_prefix' => $this->_sStylePrefix,
+            'html_id' => $this->_aHtmlIds['main'],
+            'class' => $this->_sStylePrefix . ($bShowDoFavoriteAsButton ? '-button' : '') . ($bShowDoFavoriteAsButtonSmall ? '-button-small' : ''),
+            'count' => $aFavorite['count'],
+            'do_favorite' => $this->_getDoFavorite($aParams),
+            'bx_if:show_counter' => array(
+                'condition' => $bShowCounter,
+                'content' => array(
+                    'style_prefix' => $this->_sStylePrefix,
+        			'bx_if:show_hidden' => array(
+        				'condition' => (int)$aFavorite['count'] == 0,
+        				'content' => array()
+        			),
+                    'counter' => $this->getCounter()
+                )
+            ),
+            'script' => $this->getJsScript($bDynamicMode)
+        ));
+    }
+
+    protected function _getDoFavorite($aParams = array())
+    {
+    	$bFavorited = isset($aParams['is_favorited']) && $aParams['is_favorited'] === true;
+        $bShowDoFavoriteAsButtonSmall = isset($aParams['show_do_favorite_as_button_small']) && $aParams['show_do_favorite_as_button_small'] == true;
+        $bShowDoFavoriteAsButton = !$bShowDoFavoriteAsButtonSmall && isset($aParams['show_do_favorite_as_button']) && $aParams['show_do_favorite_as_button'] == true;
+		$bDisabled = !$this->isAllowedFavorite() || $bFavorited;
+
+        $sClass = '';
+		if($bShowDoFavoriteAsButton)
+			$sClass = 'bx-btn';
+		else if ($bShowDoFavoriteAsButtonSmall)
+			$sClass = 'bx-btn bx-btn-small';
+
+		if($bDisabled)
+			$sClass .= $bShowDoFavoriteAsButton || $bShowDoFavoriteAsButtonSmall ? ' bx-btn-disabled' : 'bx-favorite-disabled';
+
+        return $this->_oTemplate->parseHtmlByName($this->_sTmplNameDoFavorite, array(
+            'style_prefix' => $this->_sStylePrefix,
+        	'html_id' => $this->_aHtmlIds['do_link'],
+            'class' => $sClass,
+            'title' => _t('_favorite_do_favorite'),
+        	'bx_if:show_onclick' => array(
+        		'condition' => !$bDisabled,
+        		'content' => array(
+        			'js_object' => $this->getJsObjectName()
+        		)
+        	),
+            'do_favorite' => $this->_getLabelDoFavorite($aParams),
+        ));
+    }
+
+    protected function _getLabelCounter($iCount)
+    {
+        return _t('_favorite_counter', $iCount);
+    }
+
+    protected function _getLabelDoFavorite($aParams = array())
+    {
+    	$bFavorited = isset($aParams['is_favorited']) && $aParams['is_favorited'] === true;
+        return $this->_oTemplate->parseHtmlByName('favorite_do_favorite_label.html', array(
+        	'bx_if:show_icon' => array(
+        		'condition' => isset($aParams['show_do_favorite_icon']) && $aParams['show_do_favorite_icon'] == true,
+        		'content' => array(
+        			'name' => $this->_getIconDoFavorite($bFavorited)
+        		)
+        	),
+        	'bx_if:show_text' => array(
+        		'condition' => isset($aParams['show_do_favorite_label']) && $aParams['show_do_favorite_label'] == true,
+        		'content' => array(
+        			'text' => _t($this->_getTitleDoFavorite($bFavorited))
+        		)
+        	)
+        ));
+    }
+
+	protected function _getFavoritedBy()
+    {
+        $aTmplFavorites = array();
+
+        $aFavorites = $this->_oQuery->getPerformedBy($this->getId());
+        foreach($aFavorites as $aFavorite) {
+            list($sUserName, $sUserUrl, $sUserIcon, $sUserUnit) = $this->_getAuthorInfo($aFavorite['author_id']);
+
+            $aTmplFavorites[] = array(
+                'style_prefix' => $this->_sStylePrefix,
+                'user_unit' => $sUserUnit,
+            );
+        }
+
+        if(empty($aTmplFavorites))
+            $aTmplFavorites = MsgBox(_t('_Empty'));
+
+        return $this->_oTemplate->parseHtmlByName('favorite_by_list.html', array(
+            'style_prefix' => $this->_sStylePrefix,
+            'bx_repeat:list' => $aTmplFavorites
+        ));
+    }
+}
+
+/** @} */

Некоторые файлы не были показаны из-за большого количества измененных файлов