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

Ticket #1630 - Changes in core and Base modules.

AntonLV пре 7 година
родитељ
комит
e235c45aea

+ 2 - 1
install/sql/system.sql

@@ -3448,7 +3448,8 @@ INSERT INTO `sys_menu_templates` (`id`, `template`, `title`, `visible`) VALUES
 (19, 'menu_floating_blocks_wide.html', '_sys_menu_template_title_floating_blocks_wide', 0),
 (20, 'menu_custom_ver.html', '_sys_menu_template_title_custom_ver', 0),
 (21, 'menu_profile_stats.html', '_sys_menu_template_title_profile_stats', 0),
-(22, 'menu_interactive.html', '_sys_menu_template_title_interactive', 1);
+(22, 'menu_interactive.html', '_sys_menu_template_title_interactive', 1),
+(23, 'menu_buttons_icon_hor.html', '_sys_menu_template_title_buttons_icon_hor', 1);
 
 CREATE TABLE IF NOT EXISTS `sys_objects_menu` (
   `id` int(11) NOT NULL AUTO_INCREMENT,

+ 15 - 3
modules/base/general/classes/BxBaseModGeneralTemplate.php

@@ -179,16 +179,28 @@ class BxBaseModGeneralTemplate extends BxDolModuleTemplate
         return $this->entryAttachmentsByStorage($this->getModule()->_oConfig->CNF['OBJECT_STORAGE'], $aData, $aParams);
     }
 
-    function entryAttachmentsByStorage ($sStorage, $aData, $aParams = array())
+    function entryAttachmentsByStorage ($mixedStorage, $aData, $aParams = array())
     {
-        if (!($a = $this->getAttachments($sStorage, $aData, $aParams)))
+        if(!is_array($mixedStorage))
+            $mixedStorage = array($mixedStorage);
+
+        $aResult = array();
+        foreach($mixedStorage as $sStorage) {
+            $aAttachments = $this->getAttachments($sStorage, $aData, $aParams);
+            if(empty($aAttachments) || !is_array($aAttachments))
+                continue;
+
+            $aResult = array_merge($aResult, $aAttachments);
+        }
+
+        if(empty($aResult) || !is_array($aResult))
             return '';
 
         $this->addCss(BX_DIRECTORY_PATH_PLUGINS_PUBLIC . 'flickity/|flickity.css');
     	$this->addJs('flickity/flickity.pkgd.min.js');
 
     	return $this->parseHtmlByName('attachments.html', array(
-            'bx_repeat:attachments' => $a,
+            'bx_repeat:attachments' => $aResult,
         ));
     }
 

+ 65 - 0
modules/base/text/classes/BxBaseModTextConfig.php

@@ -11,9 +11,74 @@
 
 class BxBaseModTextConfig extends BxBaseModGeneralConfig
 {
+    protected $_aHtmlIds;
+
+    /**
+     * Determine whether Timeline post will use common content (header image + text)
+     * or content received from attachments (images + videos + polls + text).
+     * By default the first variant is used.
+     */
+    protected $_bAttachmentsInTimeline;
+
     function __construct($aModule)
     {
         parent::__construct($aModule);
+
+        $this->CNF = array(
+            // database tables
+            'TABLE_POLLS' => $aModule['db_prefix'] . 'polls',
+            'TABLE_POLLS_ANSWERS' => $aModule['db_prefix'] . 'polls_answers',
+            'TABLE_POLLS_ANSWERS_VOTES' => $aModule['db_prefix'] . 'polls_answers_votes',
+            'TABLE_POLLS_ANSWERS_VOTES_TRACK' => $aModule['db_prefix'] . 'polls_answers_votes_track',
+
+            // database fields
+            'FIELD_POLL_ID' => 'id',
+            'FIELD_POLL_AUTHOR_ID' => 'author_id',
+            'FIELD_POLL_CONTENT_ID' => 'content_id',
+            'FIELD_POLL_TEXT' => 'text',
+            'FIELD_POLL_ANSWERS' => 'answers',
+
+            // some params
+            'PARAM_POLL_HIDDEN_RESULTS' => false,
+            'PARAM_POLL_ANONYMOUS_VOTING' => true,
+
+            // objects
+            'OBJECT_VOTES_POLL_ANSWERS' => $this->_sName . '_poll_answers',
+        );
+
+        $this->_aJsClasses = array(
+            'poll' => $this->_sClassPrefix . 'Polls'
+        );
+
+        $this->_aJsObjects = array(
+            'poll' => 'o' . $this->_sClassPrefix . 'Polls'
+        );
+
+        $sPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'add_poll_popup' =>  $sPrefix . '-add-poll-popup',
+            'add_poll_form_field' => $sPrefix . '-add-poll-form-field',
+            'poll' => $sPrefix . '-poll-',
+            'poll_content' => $sPrefix . '-poll-content-',
+            'poll_view_menu' => $sPrefix . '-poll-view-menu',
+            'poll_view_link_answers' => $sPrefix . '-poll-view-answers-',
+            'poll_view_link_results' => $sPrefix . '-poll-view-results-',
+        );
+
+        $this->_bAttachmentsInTimeline = false;
+    }
+
+    public function isAttachmentsInTimeline()
+    {
+        return $this->_bAttachmentsInTimeline;
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
     }
 }
 

+ 260 - 0
modules/base/text/classes/BxBaseModTextDb.php

@@ -48,6 +48,266 @@ class BxBaseModTextDb extends BxBaseModGeneralDb
         $sQuery = $this->prepare ("SELECT COUNT(*) FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO'] . "` = ?", - $iProfileId);
         return $this->getOne($sQuery);
     }
+
+
+    /**
+     * 
+     * Internal Polls related methods. 
+     * 
+     */
+    public function isPollPerformed($iPollId, $iAuthorId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($iAuthorId))
+            $iAuthorId = bx_get_logged_profile_id();
+
+        $aAnswers = $this->getPollAnswers(array('type' => 'poll_id_pairs', 'poll_id' => $iPollId));
+
+        return (int)$this->getOne("SELECT `object_id` FROM `" . $CNF['TABLE_POLLS_ANSWERS_VOTES_TRACK'] . "` WHERE `object_id` IN (" . $this->implode_escape(array_keys($aAnswers)) . ") AND `author_id`=:author_id LIMIT 1", array(
+            'author_id' => $iAuthorId
+        )) != 0;
+    }
+
+    public function getPolls($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sJoinClause = $sWhereClause = $sOrderByClause = "";
+
+        $sSelectClause = "`tp`.*";
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause .= " AND `tp`.`id`=:id";
+                break;
+
+            case 'answer_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'answer_id' => $aParams['answer_id']
+                );
+
+                $sJoinClause = "LEFT JOIN `" . $CNF['TABLE_POLLS_ANSWERS'] . "` AS `ta` ON `tp`.`" . $CNF['FIELD_POLL_ID'] . "`=`ta`.`poll_id`";
+                $sWhereClause .= " AND `ta`.`id`=:answer_id";
+                break;
+
+            case 'content_id':
+                $aMethod['params'][1] = array(
+                    'content_id' => $aParams['content_id']
+                );
+
+                $sWhereClause .= " AND `tp`.`content_id`=:content_id";
+                break;
+            
+            case 'content_id_ids':
+                $aMethod['name'] = 'getColumn';
+                $aMethod['params'][1] = array(
+                    'content_id' => $aParams['content_id']
+                );
+
+                $sSelectClause = "`tp`.`" . $CNF['FIELD_POLL_ID'] . "`";
+                $sWhereClause .= " AND `tp`.`content_id`=:content_id";
+                break;
+
+            case 'author_id':
+                $aMethod['params'][1] = array(
+                    'author_id' => $aParams['author_id']
+                );
+
+                $sWhereClause .= " AND `tp`.`author_id`=:author_id";
+
+                if(isset($aParams['unused']) && $aParams['unused'] === true) {
+                    $aMethod['params'][1]['content_id'] = 0;
+
+                    $sWhereClause .= " AND `tp`.`content_id`=:content_id";
+                }
+                break;
+        }
+
+        if(!empty($sOrderByClause))
+            $sOrderByClause = "ORDER BY " . $sOrderByClause;
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_POLLS'] . "` AS `tp` " . $sJoinClause . " WHERE 1 " . $sWhereClause . $sOrderByClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getPollInfoById($iId)
+    {
+        return $this->getPolls(array('type' => 'id', 'id' => $iId));
+    }
+
+    public function updatePolls($aParamsSet, $aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_POLLS'] . "` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deletePolls($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sWhereClause = $this->arrayToSQL($aParams, " AND ");
+        $aPolls = $this->getAll("SELECT * FROM `" . $CNF['TABLE_POLLS'] . "` WHERE " . $sWhereClause);
+        if(empty($aPolls) || !is_array($aPolls))
+            return true;
+
+        return $this->_deletePolls($aPolls);
+    }
+
+    public function deletePollsByIds($mixedId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sWhereClause = "`id` IN (" . $this->implode_escape(is_array($mixedId) ? $mixedId : array($mixedId)) . ")";
+        $aPolls = $this->getAll("SELECT * FROM `" . $CNF['TABLE_POLLS'] . "` WHERE " . $sWhereClause);
+        if(empty($aPolls) || !is_array($aPolls))
+            return true;
+
+        return $this->_deletePolls($aPolls);
+    }
+
+    public function getPollAnswers($aParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+        $sWhereClause = $sOrderByClause = "";
+
+        $sSelectClause = "*";
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                	'id' => $aParams['id']
+                );
+
+                $sWhereClause .= " AND `id`=:id";
+                break;
+
+            case 'poll_id_pairs':
+                $aMethod['name'] = 'getPairs';
+                $aMethod['params'][1] = 'id';
+                $aMethod['params'][2] = 'title';
+                $aMethod['params'][3] = array(
+                    'poll_id' => $aParams['poll_id']
+                );
+
+                $sWhereClause .= " AND `poll_id`=:poll_id";
+                break;
+
+            case 'poll_id':
+                $aMethod['params'][1] = array(
+                    'poll_id' => $aParams['poll_id']
+                );
+
+                $sWhereClause .= " AND `poll_id`=:poll_id";
+                break;
+
+            case 'poll_id_max_order':
+                $aMethod['name'] = 'getOne';
+                $aMethod['params'][1] = array(
+                    'poll_id' => $aParams['poll_id']
+                );
+
+                $sSelectClause = "MAX(`order`)";
+                $sWhereClause .= " AND `poll_id`=:poll_id";
+                break;
+
+            case 'all':
+            	break;
+        }
+
+        $sOrderByClause = " ORDER BY " . (isset($aParams['order_by']) ? '`' . $aParams['order_by'] . '`' : '`order`');
+        $sOrderByClause .= " " . (isset($aParams['order_way']) ? strtoupper($aParams['order_way']) : 'ASC');
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_POLLS_ANSWERS'] . "` WHERE 1 " . $sWhereClause . $sOrderByClause;
+
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function insertPollAnswer($aParamsSet)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet))
+            return false;
+
+        return $this->query("INSERT INTO `" . $CNF['TABLE_POLLS_ANSWERS'] . "` SET " . $this->arrayToSQL($aParamsSet));
+    }
+
+    public function updatePollAnswers($aParamsSet, $aParamsWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aParamsSet) || empty($aParamsWhere))
+            return false;
+
+        return $this->query("UPDATE `" . $CNF['TABLE_POLLS_ANSWERS'] . "` SET " . $this->arrayToSQL($aParamsSet) . " WHERE " . $this->arrayToSQL($aParamsWhere, " AND "));
+    }
+
+    public function deletePollAnswers($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sWhereClause = $this->arrayToSQL($aParams, " AND ");
+        $aAnswers = $this->getAll("SELECT * FROM `" . $CNF['TABLE_POLLS_ANSWERS'] . "` WHERE " . $sWhereClause);
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return true;
+
+        return $this->_deletePollAnswers($aAnswers);
+    }
+
+    public function deletePollAnswersByIds($mixedId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $sWhereClause = "`id` IN (" . $this->implode_escape(is_array($mixedId) ? $mixedId : array($mixedId)) . ")";
+        $aAnswers = $this->getAll("SELECT * FROM `" . $CNF['TABLE_POLLS_ANSWERS'] . "` WHERE " . $sWhereClause);
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return true;
+
+        return $this->_deletePollAnswers($aAnswers);
+    }
+
+    protected function _deletePolls(&$aPolls)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aAffected = array();           
+        foreach($aPolls as $aPoll) {
+            if(!$this->deletePollAnswers(array('poll_id' => $aPoll[$CNF['FIELD_POLL_ID']])))
+                continue;
+
+            $aAffected[] = $aPoll[$CNF['FIELD_POLL_ID']];
+        }
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_POLLS'] . "` WHERE `id` IN (" . $this->implode_escape($aAffected) . ")");
+    }
+
+    protected function _deletePollAnswers(&$aAnswers)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aAffected = array();
+        foreach($aAnswers as $aAnswer) {
+            BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_POLL_ANSWERS'], $aAnswer['id'])->onObjectDelete();
+
+            $aAffected[] = $aAnswer['id'];
+        }
+
+        return $this->query("DELETE FROM `" . $CNF['TABLE_POLLS_ANSWERS'] . "` WHERE `id` IN (" . $this->implode_escape($aAffected) . ")");
+    }
 }
 
 /** @} */

+ 102 - 0
modules/base/text/classes/BxBaseModTextFormEntry.php

@@ -14,10 +14,112 @@
  */
 class BxBaseModTextFormEntry extends BxBaseModGeneralFormEntry
 {
+    protected $_sGhostTemplateVideo = 'form_ghost_template_video.html';
+
     public function __construct($aInfo, $oTemplate = false)
     {
         parent::__construct($aInfo, $oTemplate);
     }
+
+    function getCode($bDynamicMode = false)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sResult = parent::getCode($bDynamicMode);
+        $sResult .= $this->_oModule->_oTemplate->getJsCode('poll');
+
+        if(!empty($CNF['OBJECT_MENU_ENTRY_ATTACHMENTS']))
+            $sResult = $this->_oModule->_oTemplate->parseHtmlByContent($sResult, array(
+                'attachments_menu' => BxDolMenu::getObjectInstance($CNF['OBJECT_MENU_ENTRY_ATTACHMENTS'])->getCode()
+            ));
+
+        $this->_oModule->_oTemplate->addJs(array('polls.js'));
+    	return $sResult;
+    }
+
+    function initChecker ($aValues = array (), $aSpecificValues = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $bValues = $aValues && !empty($aValues['id']);
+        $aContentInfo = $bValues ? $this->_oModule->_oDb->getContentInfoById($aValues['id']) : false;
+
+        if (isset($CNF['FIELD_VIDEO']) && isset($this->aInputs[$CNF['FIELD_VIDEO']])) {
+            if ($bValues)
+                $this->aInputs[$CNF['FIELD_VIDEO']]['content_id'] = $aValues['id'];
+
+            $this->aInputs[$CNF['FIELD_VIDEO']]['ghost_template'] = $this->_oModule->_oTemplate->parseHtmlByName($this->_sGhostTemplateVideo, $this->_getVideoGhostTmplVars($aContentInfo));
+        }
+
+        if (isset($CNF['FIELD_POLL']) && isset($this->aInputs[$CNF['FIELD_POLL']])) {
+            if ($bValues)
+                $this->aInputs[$CNF['FIELD_POLL']]['content_id'] = $aValues['id'];
+        }
+
+        parent::initChecker ($aValues, $aSpecificValues);
+    }
+
+    protected function genCustomInputAttachments ($aInput)
+    {
+        return '__attachments_menu__';
+    }
+
+    protected function genCustomInputPolls ($aInput)
+    {
+        $this->_oModule->_oTemplate->addCss(array('polls.css'));
+
+        return $this->_oModule->_oTemplate->getPollField(!empty($aInput['content_id']) ? (int)$aInput['content_id'] : 0);
+    }
+
+    public function processPolls ($sFieldPoll, $iContentId = 0)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if (!isset($this->aInputs[$sFieldPoll]))
+            return true;
+
+        $aPollIds = $this->getCleanValue($sFieldPoll);
+        if(empty($aPollIds) || !is_array($aPollIds))
+            return true;
+
+        $iProfileId = $this->getContentOwnerProfileId($iContentId);
+
+        $aPollsDbIds = $this->_oModule->_oDb->getPolls(array('type' => 'content_id_ids', 'content_id' => $iContentId));
+
+        //--- Remove deleted
+        $this->_oModule->_oDb->deletePollsByIds(array_diff($aPollsDbIds, $aPollIds));
+
+        //--- Add new
+        if($iContentId) {
+            $aPollsAddIds = array_diff($aPollIds, $aPollsDbIds);
+            foreach($aPollsAddIds as $iPollId)
+                $this->_oModule->_oDb->updatePolls(array($CNF['FIELD_POLL_CONTENT_ID'] => $iContentId), array($CNF['FIELD_POLL_ID'] => $iPollId, $CNF['FIELD_POLL_CONTENT_ID'] => 0));
+        }
+
+        return true;
+    }
+
+    protected function _getPhotoGhostTmplVars($aContentInfo = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return array (
+            'name' => $this->aInputs[$CNF['FIELD_PHOTO']]['name'],
+            'content_id' => (int)$this->aInputs[$CNF['FIELD_PHOTO']]['content_id'],
+            'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : ''
+    	);
+    }
+
+    protected function _getVideoGhostTmplVars($aContentInfo = array())
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	return array (
+            'name' => $this->aInputs[$CNF['FIELD_VIDEO']]['name'],
+            'content_id' => (int)$this->aInputs[$CNF['FIELD_VIDEO']]['content_id'],
+            'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : ''
+    	);
+    }
 }
 
 /** @} */

+ 27 - 0
modules/base/text/classes/BxBaseModTextFormsEntryHelper.php

@@ -69,6 +69,12 @@ class BxBaseModTextFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         if (!($aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId)))
             return MsgBox(_t('_sys_txt_error_occured'));
 
+        if (isset($CNF['FIELD_VIDEO']))
+            $oForm->processFiles($CNF['FIELD_VIDEO'], $iContentId, false);
+
+        if (isset($CNF['FIELD_POLL']))
+            $oForm->processPolls($CNF['FIELD_POLL'], $iContentId);
+
         // change profile to 'pending' only if profile is 'active'
         if ($oProfile->isActive() && !empty($aTrackTextFieldsChanges['changed_fields']))
             $oProfile->disapprove(BX_PROFILE_ACTION_AUTO);
@@ -84,15 +90,36 @@ class BxBaseModTextFormsEntryHelper extends BxBaseModGeneralFormsEntryHelper
         if ($s = parent::onDataAddAfter($iAccountId, $iContentId))
             return $s;
 
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
         if (!($aContentInfo = $this->_oModule->_oDb->getContentInfoById($iContentId)))
             return MsgBox(_t('_sys_txt_error_occured'));
 
+        if(($oForm = $this->getObjectFormAdd()) !== false) {
+            if (isset($CNF['FIELD_VIDEO']))
+                $oForm->processFiles($CNF['FIELD_VIDEO'], $iContentId, true);
+
+            if (isset($CNF['FIELD_POLL']))
+                $oForm->processPolls($CNF['FIELD_POLL'], $iContentId);
+        }
+
         // alert
         $this->_alertAfterAdd($aContentInfo);
 
         return '';
     }
 
+    public function onDataDeleteAfter($iContentId, $aContentInfo, $oProfile)
+    {
+        $sResult = parent::onDataDeleteAfter ($iContentId, $aContentInfo, $oProfile);
+        if(!empty($sResult))
+            return $sResult;
+
+        $aPolls = $this->_oModule->_oDb->deletePolls(array('content_id' => $iContentId));
+
+        return '';
+    }
+
     protected function _alertAfterAdd($aContentInfo)
     {
         $CNF = &$this->_oModule->_oConfig->CNF;

+ 205 - 0
modules/base/text/classes/BxBaseModTextModule.php

@@ -19,7 +19,69 @@ class BxBaseModTextModule extends BxBaseModGeneralModule implements iBxDolConten
         parent::__construct($aModule);
     }
 
+    // ====== ACTIONS METHODS
+    public function actionGetPoll()
+    {
+        $iPollId = (int)bx_get('poll_id');
+        $sView = bx_process_input(bx_get('view'));
+
+        $sMethod = 'serviceGetBlockPoll' . bx_gen_method_name($sView);
+        if(!method_exists($this, $sMethod))
+            return echoJson(array());
+
+        $aBlock = $this->$sMethod($iPollId, true);
+        if(empty($aBlock) || !is_array($aBlock))
+            return echoJson(array());
+
+        return echoJson(array(
+            'content' => $aBlock['content']
+        ));
+    }
+    public function actionDeletePoll()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iId = bx_process_input(bx_get('id'), BX_DATA_INT);
+        if(empty($iId))
+            return echoJson(array());
+
+        $aResult = array();
+        if($this->_oDb->deletePolls(array($CNF['FIELD_POLL_ID'] => $iId)))
+            $aResult = array('code' => 0);
+        else
+            $aResult = array('code' => 1, 'message' => _t($CNF['txt_err_cannot_perform_action']));
+
+        echoJson($aResult);
+    }
+
+    public function actionGetPollForm()
+    {
+        echo $this->_oTemplate->getPollForm();
+    }
+
+    public function actionSubmitPollForm()
+    {
+        echoJson($this->getPollForm());
+    }
+
+
     // ====== SERVICE METHODS
+    public function serviceGetBlockPollAnswers($iPollId, $bForceDisplay = false)
+    {
+        if(!$iPollId)
+            return false;
+
+        if(!$bForceDisplay && $this->_oDb->isPollPerformed($iPollId, bx_get_logged_profile_id()))
+            return $this->serviceGetBlockPollResults($iPollId);
+
+        return $this->_serviceTemplateFunc('entryPollAnswers', $iPollId, 'getPollInfoById');
+    }
+
+    public function serviceGetBlockPollResults($iPollId)
+    {
+        return $this->_serviceTemplateFunc('entryPollResults', $iPollId, 'getPollInfoById');
+    }
+
     public function serviceGetThumb ($iContentId, $sTranscoder = '') 
     {
         $CNF = &$this->_oConfig->CNF;
@@ -133,6 +195,14 @@ class BxBaseModTextModule extends BxBaseModGeneralModule implements iBxDolConten
     {
         return $this->_serviceTemplateFunc ('entryContext', $iContentId);
     }
+    
+    /**
+     * Entry polls block
+     */
+    public function serviceEntityPolls ($iContentId = 0)
+    {
+        return $this->_serviceTemplateFunc ('entryPolls', $iContentId);
+    }
 
     public function serviceEntityBreadcrumb ($iContentId = 0)
     {
@@ -197,7 +267,142 @@ class BxBaseModTextModule extends BxBaseModGeneralModule implements iBxDolConten
         return true;        
     }
 
+    public function getPollForm()
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($CNF['OBJECT_FORM_POLL']))
+            return array('code' => 1, 'message' => '_sys_txt_error_occured');
+
+        $iProfileId = bx_get_logged_profile_id();
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_POLL'], $CNF['OBJECT_FORM_POLL_DISPLAY_ADD'], $this->_oTemplate);
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'submit_poll_form/';
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iId = $oForm->insert();
+            if($iId)
+                return array('code' => 0, 'id' => $iId, 'item' => $this->_oTemplate->getPollItem($iId, $iProfileId, array(
+                    'manage' => true
+                )));
+            else
+                return array('code' => 2, 'message' => '_sys_txt_error_entry_creation');
+        }
+
+        return array('form' => $oForm->getCode(), 'form_id' => $oForm->id);
+    }
+
+    
     // ====== PROTECTED METHODS
+    protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isAttachmentsInTimeline() || empty($CNF['OBJECT_STORAGE_PHOTOS']) || empty($CNF['OBJECT_IMAGES_TRANSCODER_GALLERY_PHOTOS']))
+            return parent::_getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE_PHOTOS']);
+
+        $aGhostFiles = $oStorage->getGhosts($this->serviceGetContentOwnerProfileId($iContentId), $iContentId);
+        if(!$aGhostFiles)
+            return array();
+
+        $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS']);
+
+        $aResults = array();
+        foreach ($aGhostFiles as $k => $a) {
+            $sPhotoSrc = $oTranscoder->getFileUrl($a['id']);
+            if(empty($sPhotoSrc))
+                continue;
+
+            $aResults[] = array(
+                'src' => $sPhotoSrc,
+                'src_medium' => $sPhotoSrc,
+                'src_orig' => $oStorage->getFileUrlById($a['id']),
+            );
+        }
+
+        return $aResults;
+    }
+    
+    protected function _getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$this->_oConfig->isAttachmentsInTimeline() || empty($CNF['OBJECT_STORAGE_VIDEOS']) || empty($CNF['OBJECT_VIDEOS_TRANSCODERS']))
+            return parent::_getVideosForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams);
+
+        $iContentId = (int)$aContentInfo[$CNF['FIELD_ID']];
+        $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE_VIDEOS']);
+
+        $aGhostFiles = $oStorage->getGhosts($this->serviceGetContentOwnerProfileId($iContentId), $iContentId);
+        if(!$aGhostFiles)
+            return array();
+
+        $oTcPoster = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['poster']);
+        $oTcMp4 = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['mp4']);
+        $oTcWebm = BxDolTranscoderVideo::getObjectInstance($CNF['OBJECT_VIDEOS_TRANSCODERS']['webm']);
+        if(!$oTcPoster || !$oTcMp4 || !$oTcWebm)
+            return array();
+
+        $aResults = array();
+        foreach ($aGhostFiles as $k => $a) {
+
+            $aResults[$a['id']] = array(
+                'id' => $a['id'],
+                'src_poster' => $oTcPoster->getFileUrl($a['id']),
+                'src_mp4' => $oTcMp4->getFileUrl($a['id']),
+                'src_webm' => $oTcWebm->getFileUrl($a['id']),
+            );
+        }
+
+        return $aResults;
+    }
+
+    protected function _getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams = array())
+    {
+        $aResult = parent::_getContentForTimelinePost($aEvent, $aContentInfo, $aBrowseParams);
+        if(!$this->_oConfig->isAttachmentsInTimeline())
+            return $aResult;
+
+        $CNF = &$this->_oConfig->CNF;
+
+        $iProfile = bx_get_logged_profile_id();
+        $bDynamic = isset($aBrowseParams['dynamic_mode']) && $aBrowseParams['dynamic_mode'] === true;
+
+        $sPolls = '';
+        $aPolls = $this->_oDb->getPolls(array('type' => 'content_id', 'content_id' => (int)$aContentInfo[$CNF['FIELD_ID']]));
+        if(!empty($aPolls) && is_array($aPolls)) {
+            foreach($aPolls as $aPoll) {
+                $sPoll = $this->_oTemplate->getPollItem($aPoll, $iProfile, array(
+                    'dynamic' => $bDynamic,
+                    'switch_menu' => false
+                ));
+                if(empty($sPoll))
+                    continue;
+
+                $sPolls .= $sPoll;
+            }
+        
+            if(!empty($sPolls)) {
+                $sInclude = '';
+                $sInclude .= $this->_oTemplate->addJs(array('polls.js'), $bDynamic);
+                $sInclude .= $this->_oTemplate->addCss(array('polls.css'), $bDynamic);
+
+                $aResult['raw'] = ($bDynamic ? $sInclude : '') . $this->_oTemplate->parseHtmlByName('poll_items.html', array(
+                    'bx_if:show_autosizing' => array(
+                        'condition' => false,
+                        'content' => array()
+                    ),
+                    'polls' => $sPolls
+                )) . $this->_oTemplate->getJsCode('poll');
+            }
+        }
+
+        return $aResult;
+    }
 
     protected function _buildRssParams($sMode, $aArgs)
     {

+ 281 - 2
modules/base/text/classes/BxBaseModTextTemplate.php

@@ -23,6 +23,15 @@ class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
         $this->_sUnitClassShowCase = 'bx-base-unit-showcase bx-base-text-unit-showcase bx-def-margin-sec-bottom';
     }
 
+    public function getJsCode($sType, $aParams = array(), $bWrap = true)
+    {
+        $aParams = array_merge(array(
+            'aHtmlIds' => $this->_oConfig->getHtmlIds()
+        ), $aParams);
+
+        return parent::getJsCode($sType, $aParams, $bWrap);
+    }
+
     function unit ($aData, $isCheckPrivateContent = true, $sTemplateName = 'unit.html', $aParams = array())
     {
     	$sResult = $this->checkPrivacy ($aData, $isCheckPrivateContent, $this->getModule(), $sTemplateName);
@@ -32,6 +41,20 @@ class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
 		return $this->parseHtmlByName($sTemplateName, $this->getUnit($aData, $aParams));
     }
 
+    function entryAttachments ($aData, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aStoragesKeys = array('OBJECT_STORAGE_PHOTOS', 'OBJECT_STORAGE_VIDEOS');
+
+        $aStorages = array();
+        foreach($aStoragesKeys as $sKey)
+            if(!empty($CNF[$sKey]))
+                $aStorages[] = $CNF[$sKey];
+
+        return $this->entryAttachmentsByStorage($aStorages, $aData, $aParams);
+    }
+
     function entryAuthor ($aData, $iProfileId = false, $sFuncAuthorDesc = 'getAuthorDesc', $sTemplateName = 'author.html', $sFuncAuthorAddon = 'getAuthorAddon')
     {
         $CNF = &$this->getModule()->_oConfig->CNF;
@@ -97,6 +120,247 @@ class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
         return $this->parseHtmlByName($sTemplateName, $aVars);
     }
 
+    public function entryPolls($aData)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        
+        $aPolls = $this->_oDb->getPolls(array(
+            'type' => 'content_id', 
+            'content_id' => $aData[$CNF['FIELD_ID']], 
+        ));
+        if(empty($aPolls) || !is_array($aPolls))
+            return;
+
+        $iProfileId = bx_get_logged_profile_id();
+
+        $sPolls = '';
+        foreach($aPolls as $aPoll)
+            $sPolls .= $this->getPollItem($aPoll, $iProfileId);
+
+        if(empty($sPolls))
+            return '';
+
+        $this->addCss(array('polls.css'));
+        $this->addJs(array('polls.js'));
+        return $this->parseHtmlByName('poll_items.html', array(
+            'bx_if:show_autosizing' => array(
+                'condition' => true,
+                'content' => array()
+            ),
+            'polls' => $sPolls
+        ));
+    }
+
+    public function entryPollAnswers($aPoll, $bDynamic = false)
+    {
+        $sContent = $this->_getPollAnswers($aPoll, $bDynamic);
+        if(empty($sContent))
+            return '';
+
+    	return array(
+            'content' => $sContent,
+            'menu' => $this->_getPollBlockMenu($aPoll, 'answers')
+        ); 
+    }
+
+    public function entryPollResults($aPoll, $bDynamic = false)
+    {
+        $sContent = $this->_getPollResults($aPoll, $bDynamic);
+        if(empty($sContent))
+            return '';
+
+        return array(
+            'content' => $sContent,
+            'menu' => $this->_getPollBlockMenu($aPoll, 'results')
+        );  
+    }
+
+    public function getPollForm()
+    {
+        $aForm = $this->getModule()->getPollForm();
+
+        return $this->parseHtmlByName('poll_form.html', array(
+            'js_object' => $this->_oConfig->getJsObject('poll'),
+            'form_id' => $aForm['form_id'],
+            'form' => $aForm['form'],
+        ));
+    }
+    
+    public function getPollField($iContentId = 0, $iProfileId = 0)
+    {
+        if(empty($iProfileId))
+            $iProfileId = bx_get_logged_profile_id();
+
+        $aPolls = array();
+        if(!empty($iContentId))
+            $aPolls = array_merge($aPolls, $this->_oDb->getPolls(array(
+                'type' => 'content_id', 
+                'content_id' => $iContentId, 
+            )));
+
+        $aPolls = array_merge($aPolls, $this->_oDb->getPolls(array(
+            'type' => 'author_id', 
+            'author_id' => $iProfileId, 
+            'unused' => true
+        )));
+
+        $sPolls = '';
+        foreach($aPolls as $aPoll)
+            $sPolls .= $this->getPollItem($aPoll, $iProfileId, array(
+                'manage' => true
+            ));
+
+        return $this->parseHtmlByName('poll_form_field.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('add_poll_form_field'),
+            'polls' => $sPolls
+        ));
+    }
+
+    public function getPollItem($mixedPoll, $iProfileId = 0, $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aPoll = is_array($mixedPoll) ? $mixedPoll : $this->_oDb->getPolls(array('type' => 'id', 'id' => (int)$mixedPoll));
+        if(empty($aPoll) || !is_array($aPoll))
+            return '';
+
+        $sJsObject = $this->_oConfig->getJsObject('poll');
+
+        $bDynamic = isset($aParams['dynamic']) && $aParams['dynamic'] === true;
+        $bManage = isset($aParams['manage']) && $aParams['manage'] === true;
+        $bSwitchMenu = isset($aParams['switch_menu']) ? (bool)$aParams['switch_menu'] : true;
+        $bForceDisplayAnswers = isset($aParams['force_display_answers']) && (bool)$aParams['force_display_answers'] === true;
+
+        $iPollId = (int)$aPoll[$CNF['FIELD_POLL_ID']];
+        $sPollView = !$bForceDisplayAnswers && $this->_oDb->isPollPerformed($iPollId, $iProfileId) ? 'results' : 'answers';
+        
+        $sMethod = '_getPoll' . ucfirst($sPollView);
+        if(!method_exists($this, $sMethod))
+            return '';
+
+        $mixedMenu = '';
+        if($bSwitchMenu)
+            $mixedMenu = $this->_getPollBlockMenu($aPoll, $sPollView, array('template' => 'menu_interactive.html'));
+
+        return $this->parseHtmlByName('poll_item.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll') . $iPollId,
+            'bx_if:show_input_hidden' => array(
+                'condition' => $bManage,
+                'content' => array(
+                    'name' => $CNF['FIELD_POLL'],
+                    'id' => $iPollId
+                )
+            ),
+            'action_menu' => !empty($mixedMenu) ? $mixedMenu->getCode() : '',
+            'bx_if:show_action_delete' => array(
+                'condition' => $bManage,
+                'content' => array(
+                    'js_object' => $sJsObject,
+                    'id' => $iPollId
+                )
+            ),
+            'text' => bx_process_output($aPoll[$CNF['FIELD_POLL_TEXT']], BX_DATA_HTML),
+            'content' => $this->$sMethod($aPoll, $bDynamic)
+        ));
+    }
+    
+    protected function _getPollAnswers($aPoll, $bDynamic = false)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+        
+        $aAnswers = $this->_oDb->getPollAnswers(array('type' => 'poll_id', 'poll_id' => $aPoll[$CNF['FIELD_POLL_ID']]));
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return '';
+
+        $aTmplVarsAnswers = array();
+        foreach($aAnswers as $aAnswer) {
+            $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_POLL_ANSWERS'], $aAnswer['id']);
+
+            $aTmplVarsAnswers[] = array(
+                'answer' => $oVotes->getElementBlock(array(
+                    'dynamic_mode' => $bDynamic
+                ))
+            );
+        }
+
+    	return $this->parseHtmlByName('poll_item_answers.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll_content') . $aPoll[$CNF['FIELD_POLL_ID']],
+            'bx_repeat:answers' => $aTmplVarsAnswers
+        ));
+    }
+    
+    protected function _getPollResults($aPoll, $bDynamic = false)
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $aAnswers = $this->_oDb->getPollAnswers(array('type' => 'poll_id', 'poll_id' => $aPoll[$CNF['FIELD_POLL_ID']]));
+        if(empty($aAnswers) || !is_array($aAnswers))
+            return '';
+
+        $iTotal = 0;
+        foreach($aAnswers as $aAnswer)
+            $iTotal += $aAnswer['votes'];
+
+        $aTmplVarsAnswers = array();
+        foreach($aAnswers as $aAnswer) {
+            $oVotes = BxDolVote::getObjectInstance($CNF['OBJECT_VOTES_POLL_ANSWERS'], $aAnswer['id']);
+
+            $fPercent = $iTotal > 0 ? 100 * (float)$aAnswer['votes']/$iTotal : 0;
+            $aTmplVarsAnswers[] = array(
+                'title' => bx_process_output($aAnswer['title']),
+                'width' => (int)round($fPercent) . '%',
+                'votes' => $oVotes->getCounter(array('show_counter_empty' => true, 'show_counter_in_brackets' => false)),
+                'percent' => _t($CNF['T']['txt_poll_answer_vote_percent'], $iTotal > 0 ? round($fPercent, 2) : 0),
+                'js_code' => $oVotes->getJsScript($bDynamic)
+            );
+        }
+
+        return $this->parseHtmlByName('poll_item_results.html', array(
+            'html_id' => $this->_oConfig->getHtmlIds('poll_content') . $aPoll[$CNF['FIELD_POLL_ID']],
+            'bx_repeat:answers' => $aTmplVarsAnswers,
+        ));
+    }
+
+    protected function _getPollBlockMenu($aPoll, $sSelected = '', $aParams = array())
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $sPostfix = '-' . time() . rand(0, PHP_INT_MAX);
+        $sJsObject = $this->_oConfig->getJsObject('poll');
+        $iPollId = $aPoll[$CNF['FIELD_POLL_ID']];
+
+        $aViews = array(
+            'answers' => true, 
+            'results' => $CNF['PARAM_POLL_HIDDEN_RESULTS'] === false || $this->_oDb->isPollPerformed($iPollId, bx_get_logged_profile_id())
+        );
+
+        $aMenu = array();
+        foreach($aViews as $sView => $bActive) {
+            if(!$bActive) 
+                continue;
+
+            $sId = $this->_oConfig->getHtmlIds('poll_view_link_' . $sView) . $iPollId . $sPostfix;
+            if(!empty($sSelected) && $sSelected == $sView)
+                $sSelected = $sId;
+
+            $aMenu[] = array('id' => $sId, 'name' => $sId, 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => 'javascript:' . $sJsObject . '.changePollView(this, \'' . $sView . '\', ' . $iPollId . ')', 'target' => '_self', 'title' => _t('_bx_posts_txt_poll_view_' . $sView));
+        }
+
+        if(count($aMenu) <= 1)
+            return '';
+
+        $oMenu = new BxTemplMenuInteractive(array(
+            'template' => !empty($aParams['template']) ? $aParams['template'] : 'menu_interactive_vertical.html', 
+            'menu_id' => $this->_oConfig->getHtmlIds('poll_view_menu') . $sPostfix, 
+            'menu_items' => $aMenu
+        ));
+
+        if(!empty($sSelected))
+            $oMenu->setSelected('', $sSelected);
+
+        return $oMenu;
+    }
+
     public function getTmplVarsText($aData)
     {
         $aVars = parent::getTmplVarsText($aData);
@@ -204,7 +468,7 @@ class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
         return array($sPhotoThumb, $sPhotoGallery);
     }
 
-	protected function getUnit ($aData, $aParams = array())
+    protected function getUnit ($aData, $aParams = array())
     {
         $CNF = &BxDolModule::getInstance($this->MODULE)->_oConfig->CNF;
 
@@ -306,7 +570,22 @@ class BxBaseModTextTemplate extends BxBaseModGeneralTemplate
         $sLinkMore = ' <a title="' . bx_html_attribute(_t('_sys_read_more', $sTitle)) . '" href="' . $sUrl . '"><i class="sys-icon ellipsis-h"></i></a>';
         return  strmaxtextlen($sText, (int)getParam($CNF['PARAM_CHARS_SUMMARY']), $sLinkMore);
     }
-    
+
+    protected function getAttachmentsImagesTranscoders ()
+    {
+        $CNF = &$this->getModule()->_oConfig->CNF;
+
+        $oTranscoder = null;
+        if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS']))
+            $oTranscoder = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PREVIEW_PHOTOS']);
+
+        $oTranscoderView = null; 
+        if(!empty($CNF['OBJECT_IMAGES_TRANSCODER_PICTURE_PHOTOS']))
+            $oTranscoderView = BxDolTranscoderImage::getObjectInstance($CNF['OBJECT_IMAGES_TRANSCODER_PICTURE_PHOTOS']);
+
+        return array($oTranscoder, $oTranscoderView);
+    }
+
     protected function _getUnitClass($aData, $sTemplateName = 'unit.html')
     {
         $sResult = '';

+ 10 - 1
modules/base/text/template/css/forms.css

@@ -1,3 +1,12 @@
-
 @import url(../../../general/template/css/forms.css);
 
+/*-- Entry Form: Attachments custom elements --*/
+#bx-form-element-pictures,
+#bx-form-element-videos,
+#bx-form-element-polls {
+    margin-top: 0;
+}
+#bx-form-element-pictures .bx-form-input-files-uploaders,
+#bx-form-element-videos .bx-form-input-files-uploaders {
+    margin-top: 0;
+}

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

@@ -2322,7 +2322,8 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_menu_set_title_toolbar_member"><![CDATA[Toolbar Member]]></string>
 	<string name="_sys_menu_set_title_toolbar_site"><![CDATA[Toolbar Site]]></string>
 	<string name="_sys_menu_set_title_snippet_meta"><![CDATA[Snippet Meta Info]]></string>
-	<string name="_sys_menu_template_title_buttons_hor"><![CDATA[Horizontal Buttons]]></string>	
+	<string name="_sys_menu_template_title_buttons_hor"><![CDATA[Horizontal Buttons]]></string>
+        <string name="_sys_menu_template_title_buttons_icon_hor"><![CDATA[Horizontal Buttons (icons only)]]></string>
 	<string name="_sys_menu_template_title_buttons_ver"><![CDATA[Vertical Buttons]]></string>
 	<string name="_sys_menu_template_title_floating_blocks"><![CDATA[Floating Blocks]]></string>
 	<string name="_sys_menu_template_title_floating_blocks_wide"><![CDATA[Wide Floating Blocks with Notifications]]></string>

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

@@ -1923,7 +1923,7 @@
 	<string name="_sys_form_account_input_password_confirm_error"><![CDATA[Не удалось подтвердить пароль]]></string>
 	<string name="_sys_form_account_input_password_current"><![CDATA[Текущий пароль]]></string>
 	<string name="_sys_form_account_input_password_current_error"><![CDATA[Неправильный текущий пароль. Введите пароль, который Вы используете для доступа к этому сайту.]]></string>
-	<string name="_sys_form_account_input_password_error"><![CDATA[Пароль должен иметь как минимум 8 символов, и содержать по крайней мере 1 заглавную латинскую букву, 1 строчную латинскую букву и 1 число.]]></string>
+	<string name="_sys_form_account_input_password_error"><![CDATA[Пароль должен иметь как минимум 8 символов, и содержать по крайней мере 1 прописную латинскую букву, 1 строчную латинскую букву и 1 число.]]></string>
 	<string name="_sys_form_account_input_phone_error"><![CDATA[Это обязательное поле.]]></string>
 	<string name="_sys_form_account_input_receive_news"><![CDATA[Получать новости]]></string>
 	<string name="_sys_form_account_input_receive_updates"><![CDATA[Получать обновления сайта]]></string>
@@ -2320,6 +2320,7 @@
 	<string name="_sys_menu_set_title_toolbar_site"><![CDATA[Панель инструментов - Сайт]]></string>
 	<string name="_sys_menu_set_title_snippet_meta"><![CDATA[Мета информация в карточке]]></string>
 	<string name="_sys_menu_template_title_buttons_hor"><![CDATA[Горизонтальные кнопки]]></string>
+        <string name="_sys_menu_template_title_buttons_icon_hor"><![CDATA[Горизонтальные кнопки (только иконки)]]></string>
 	<string name="_sys_menu_template_title_buttons_ver"><![CDATA[Вертикальные кнопки]]></string>
 	<string name="_sys_menu_template_title_floating_blocks"><![CDATA[Подвесные блоки]]></string>
 	<string name="_sys_menu_template_title_floating_blocks_wide"><![CDATA[Широкие подвесные блоки с оповещениями]]></string>

+ 12 - 7
template/css/menu.css

@@ -524,16 +524,21 @@ li.bx-menu-inter-hidden {
 /* --------------- horizontal buttons menu */
 
 ul.bx-menu-btns-ver,
-ul.bx-menu-btns-hor {
-	padding:0;
+ul.bx-menu-btns-hor,
+ul.bx-menu-btns-icon-hor {
+    padding:0;
     margin:0;
-	list-style:none;
+    list-style:none;
+}
+ul.bx-menu-btns-ver,
+ul.bx-menu-btns-hor {
     text-align:center;
 }
-ul.bx-menu-btns-hor li {
+ul.bx-menu-btns-hor li,
+ul.bx-menu-btns-icon-hor li {
     display:inline;
     display:inline-block;
-	vertical-align: bottom;
+    vertical-align: bottom;
 }
 ul.bx-menu-btns-ver li {
     text-align:center;
@@ -542,8 +547,8 @@ ul.bx-menu-btns-ver li .bx-btn {
     float:none;
     display:inline-block;
 }
-
-ul.bx-menu-btns-hor li:last-child .bx-btn {
+ul.bx-menu-btns-hor li:last-child .bx-btn,
+ul.bx-menu-btns-icon-hor li:last-child .bx-btn {
     margin-right:0;
 }
 

+ 2 - 4
template/menu_buttons_hor.html

@@ -1,9 +1,7 @@
 <ul class="bx-menu-btns-hor bx-menu-object-__object__ bx-clearfix">
-    <bx_repeat:menu_items><li class="bx-def-margin-thd-topbottom __class_add__"><a class="bx-btn bx-def-margin-right" href="__link__" target="__target__" onclick="__onclick__">
+    <bx_repeat:menu_items><li class="bx-def-margin-thd-topbottom __class_add__"><a class="bx-btn bx-def-margin-right" href="__link__" target="__target__" onclick="__onclick__" title="__title_attr__">
         <bx_if:image><img style="background-image:url(__icon_url__)" src="<bx_icon_url:spacer.gif />" /></bx_if:image>
         <bx_if:icon><i class="sys-icon __icon__"></i></bx_if:icon>
-        <bx_if:title>
-        	<u>__title__</u>
-        </bx_if:title>
+        <bx_if:title><u>__title__</u></bx_if:title>
     </a></li></bx_repeat:menu_items>
 </ul>

+ 1 - 2
template/menu_buttons_hor_item.html

@@ -1,5 +1,4 @@
-<a class="bx-btn bx-def-margin-right" href="__link__" target="__target__" onclick="__onclick__">
+<a class="bx-btn bx-def-margin-right" href="__link__" target="__target__" onclick="__onclick__" title="__title_attr__">
     <bx_if:image><img style="background-image:url(__icon_url__)" src="<bx_icon_url:spacer.gif />" /></bx_if:image>
     <bx_if:icon><i class="sys-icon __icon__"></i></bx_if:icon>
-    <bx_if:title><u>__title__</u></bx_if:title>
 </a>