Browse Source

Merge branch 'master' of https://github.com/unaio/una

Alexander 6 years ago
parent
commit
c2a26df650
55 changed files with 2592 additions and 207 deletions
  1. 4 3
      inc/classes/BxDolPrivacy.php
  2. 1 2
      modules/base/general/classes/BxBaseModGeneralDb.php
  3. 30 0
      modules/base/general/classes/BxBaseModGeneralModule.php
  4. 34 0
      modules/base/groups/classes/BxBaseModGroupsConfig.php
  5. 54 0
      modules/base/groups/classes/BxBaseModGroupsCronPruning.php
  6. 227 30
      modules/base/groups/classes/BxBaseModGroupsDb.php
  7. 69 8
      modules/base/groups/classes/BxBaseModGroupsGridConnections.php
  8. 97 0
      modules/base/groups/classes/BxBaseModGroupsGridPrices.php
  9. 220 0
      modules/base/groups/classes/BxBaseModGroupsGridPricesManage.php
  10. 120 0
      modules/base/groups/classes/BxBaseModGroupsGridPricesView.php
  11. 23 0
      modules/base/groups/classes/BxBaseModGroupsMenuSnippetMeta.php
  12. 5 0
      modules/base/groups/classes/BxBaseModGroupsMenuViewActionsAll.php
  13. 375 17
      modules/base/groups/classes/BxBaseModGroupsModule.php
  14. 19 0
      modules/base/groups/template/popup_price.html
  15. 9 0
      modules/boonex/classes/classes/BxClssAlertsResponse.php
  16. 2 0
      modules/boonex/classes/classes/BxClssConfig.php
  17. 131 0
      modules/boonex/classes/classes/BxClssDb.php
  18. 38 0
      modules/boonex/classes/classes/BxClssFormEntry.php
  19. 322 0
      modules/boonex/classes/classes/BxClssModule.php
  20. 12 0
      modules/boonex/classes/classes/BxClssPageEntry.php
  21. 14 1
      modules/boonex/classes/install/langs/en.xml
  22. 6 0
      modules/boonex/classes/install/sql/enable.sql
  23. 76 36
      modules/boonex/classes/install/sql/install.sql
  24. 2 2
      modules/boonex/classes/install/sql/uninstall.sql
  25. 102 0
      modules/boonex/classes/js/main.js
  26. 31 0
      modules/boonex/classes/template/classes_add_module_form.html
  27. 28 0
      modules/boonex/classes/template/classes_class_row.html
  28. 16 0
      modules/boonex/classes/template/classes_in_context.html
  29. 30 0
      modules/boonex/classes/template/css/main.css
  30. 5 0
      modules/boonex/english/data/langs/system/en.xml
  31. 27 10
      modules/boonex/groups/classes/BxGroupsConfig.php
  32. 22 0
      modules/boonex/groups/classes/BxGroupsCronPruning.php
  33. 22 0
      modules/boonex/groups/classes/BxGroupsGridPricesManage.php
  34. 7 5
      modules/boonex/groups/classes/BxGroupsGridPricesView.php
  35. 10 0
      modules/boonex/groups/classes/BxGroupsMenuViewActionsAll.php
  36. 17 0
      modules/boonex/groups/install/installer.php
  37. 67 3
      modules/boonex/groups/install/langs/en.xml
  38. 7 4
      modules/boonex/groups/install/sql/disable.sql
  39. 77 25
      modules/boonex/groups/install/sql/enable.sql
  40. 64 0
      modules/boonex/groups/install/sql/install.sql
  41. 2 2
      modules/boonex/groups/install/sql/uninstall.sql
  42. 33 0
      modules/boonex/groups/js/prices.js
  43. 17 0
      modules/boonex/groups/template/css/prices.css
  44. 1 0
      modules/boonex/groups/template/popup_price.html
  45. 8 3
      modules/boonex/organizations/classes/BxOrgsConfig.php
  46. 15 3
      modules/boonex/organizations/classes/BxOrgsModule.php
  47. 3 1
      modules/boonex/posts/install/sql/enable.sql
  48. 66 2
      modules/boonex/russian/data/langs/bx_groups/ru.xml
  49. 5 0
      modules/boonex/russian/data/langs/system/ru.xml
  50. 0 1
      modules/boonex/tasks/classes/BxTasksConfig.php
  51. 1 6
      modules/boonex/tasks/install/sql/disable.sql
  52. 0 34
      modules/boonex/tasks/install/sql/enable.sql
  53. 0 4
      modules/boonex/tasks/install/sql/install.sql
  54. 0 2
      modules/boonex/tasks/install/sql/uninstall.sql
  55. 19 3
      template/scripts/BxBaseFormView.php

+ 4 - 3
inc/classes/BxDolPrivacy.php

@@ -402,13 +402,14 @@ class BxDolPrivacy extends BxDolFactory implements iBxDolFactoryObject
                 continue;
 
             $bActAsProfile = BxDolService::call($aModule['name'], 'act_as_profile');
-            if ($bActAsProfile && $bProfileProcessed)
+            $oModule = BxDolModule::getInstance($aModule['name']);
+            
+            if ($bActAsProfile && $bProfileProcessed && (!isset($oModule->_oConfig->CNF['ALLOW_AS_CONTEXT']) || $oModule->_oConfig->CNF['ALLOW_AS_CONTEXT'] == false))
                 continue;
             if ($bActAsProfile)
                 $bProfileProcessed = true;
-
             $a = BxDolService::call($aModule['name'], 'get_participating_profiles', array($oProfile->id()));
-            $aSpaces = array();            
+            $aSpaces = array();       
             foreach ($a as $iProfileId) {
                 if (!($o = BxDolProfile::getInstance($iProfileId)))
                     continue;

+ 1 - 2
modules/base/general/classes/BxBaseModGeneralDb.php

@@ -51,7 +51,6 @@ class BxBaseModGeneralDb extends BxDolModuleDb
                 break;
 
             case 'author':
-            	$aMethod['name'] = 'getRow';
                 $aMethod['params'][1]['author'] = (int)$aParams['author'];
 
                 $sWhereClause .= " AND `" . $CNF['TABLE_ENTRIES'] . "`.`" . $CNF['FIELD_AUTHOR'] . "` = :author";
@@ -83,7 +82,7 @@ class BxBaseModGeneralDb extends BxDolModuleDb
         $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_ENTRIES'] . "` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " " . $sOrderClause . " " . $sLimitClause;
 		return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
     }
-    
+
     public function getEntriesNumByContext ($iProfileId)
     {
         $sQuery = $this->prepare ("SELECT COUNT(*) FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `" . $this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO'] . "` = ?", - $iProfileId);

+ 30 - 0
modules/base/general/classes/BxBaseModGeneralModule.php

@@ -1973,6 +1973,22 @@ class BxBaseModGeneralModule extends BxDolModule
         // moderator and owner always have access
         if ($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || $this->_isModerator($isPerformAction))
             return CHECK_ACTION_RESULT_ALLOWED;
+        
+        // check for context's admins 
+        if (isset($this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']) && (int)$aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']] < 0){
+            $oProfile = BxDolProfile::getInstance(-(int)$aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']]);
+            if ($oProfile){
+                $sModule = $oProfile->getModule();
+                $aEntity = BxDolRequest::serviceExists($sModule, 'get_all') ? BxDolService::call($sModule, 'get_all', array(array('type' => 'id', 'id' => $oProfile->getContentId()))) : array();
+                
+                $oModule = BxDolModule::getInstance($sModule);
+                if(isset($aEntity) && $oModule->checkAllowedEdit($aEntity) === CHECK_ACTION_RESULT_ALLOWED){
+                    return CHECK_ACTION_RESULT_ALLOWED;
+                }
+            }
+        }
+     
+        
         return _t('_sys_txt_access_denied');
     }
 
@@ -1989,6 +2005,20 @@ class BxBaseModGeneralModule extends BxDolModule
         $aCheck = checkActionModule($this->_iProfileId, 'delete entry', $this->getName(), $isPerformAction);
         if (($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId || -$aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']] == $this->_iProfileId) && $aCheck[CHECK_ACTION_RESULT] === CHECK_ACTION_RESULT_ALLOWED)
             return CHECK_ACTION_RESULT_ALLOWED;
+        
+        // check for context's admins 
+        if (isset($this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']) && (int)$aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']] < 0){
+            $oProfile = BxDolProfile::getInstance(-(int)$aDataEntry[$this->_oConfig->CNF['FIELD_ALLOW_VIEW_TO']]);
+            if ($oProfile){
+                $sModule = $oProfile->getModule();
+                $aEntity = BxDolRequest::serviceExists($sModule, 'get_all') ? BxDolService::call($sModule, 'get_all', array(array('type' => 'id', 'id' => $oProfile->getContentId()))) : array();
+                
+                $oModule = BxDolModule::getInstance($sModule);
+                if(isset($aEntity) && $oModule->checkAllowedDelete($aEntity) === CHECK_ACTION_RESULT_ALLOWED){
+                    return CHECK_ACTION_RESULT_ALLOWED;
+                }
+            }
+        }
 
         return _t('_sys_txt_access_denied');
     }

+ 34 - 0
modules/base/groups/classes/BxBaseModGroupsConfig.php

@@ -11,11 +11,16 @@
 
 class BxBaseModGroupsConfig extends BxBaseModProfileConfig
 {
+    protected $_aHtmlIds;
+
+    protected $_aCurrency;
+
     function __construct($aModule)
     {
         parent::__construct($aModule);
 
         $this->_aMenuItems2MethodsActions = array (
+            'join-group-profile' => 'checkAllowedFanAdd',
             'profile-fan-add' => 'checkAllowedFanAdd',
             'profile-fan-remove' => 'checkAllowedFanRemove',
             'profile-subscribe-add' => 'checkAllowedSubscribeAdd',
@@ -23,6 +28,35 @@ class BxBaseModGroupsConfig extends BxBaseModProfileConfig
             'profile-actions-more' => 'checkAllowedViewMoreMenu',
             'convos-compose' => 'checkAllowedCompose',
         );
+
+        $sHtmlPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'popup_price' => $sHtmlPrefix . '-popup-price'
+        );
+
+        $oPayments = BxDolPayments::getInstance();
+        $this->_aCurrency = array(
+            'code' => $oPayments->getOption('default_currency_code'),
+            'sign' => $oPayments->getOption('default_currency_sign')
+        );
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
+    }
+
+    public function getCurrency()
+    {
+    	return $this->_aCurrency;
+    }
+
+    public function isPaidJoin()
+    {
+        return !empty($this->CNF['PARAM_PER_PAID_JOIN']) && getParam($this->CNF['PARAM_PER_PAID_JOIN']) == 'on';
     }
 }
 

+ 54 - 0
modules/base/groups/classes/BxBaseModGroupsCronPruning.php

@@ -0,0 +1,54 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+class BxBaseModGroupsCronPruning extends BxDolCron
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct()
+    {
+        $this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct();
+    }
+
+    function processing()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $oPayments = BxDolPayments::getInstance();
+
+        $aRoles = $this->_oModule->_oDb->getRoles(array('type' => 'expired'));
+        foreach($aRoles as $aRole) {
+            $aSubscription = $oPayments->getSubscriptionsInfo(array('subscription_id' => $aRole['order']), true);
+            if(!empty($aSubscription) && is_array($aSubscription)) {
+                $aSubscription = array_shift($aSubscription);
+
+                $iRecurringReserve = 86400 * (int)getParam($CNF['PARAM_RECURRING_RESERVE']);
+                if($aSubscription['data']['status'] == 'active') {
+                    $this->_oModule->_oDb->updateRoles(array('expired' => $aSubscription['data']['cperiod_end'] + $iRecurringReserve), array('id' => $aRole['id']));
+                    continue;
+                }
+                else {
+                    $oPayments->sendSubscriptionExpirationLetters($aSubscription['pending_id'], $aRole['order']);
+
+                    $this->_oModule->_oDb->updateRoles(array('expired' => $aRole['expired'] + $iRecurringReserve), array('id' => $aRole['id']));
+                    continue;
+                }
+            }
+
+            $this->_oModule->unsetRole($aRole['group_profile_id'], $aRole['fan_id']);
+        }
+    }
+}
+
+/** @} */

+ 227 - 30
modules/base/groups/classes/BxBaseModGroupsDb.php

@@ -88,16 +88,28 @@ class BxBaseModGroupsDb extends BxBaseModProfileDb
 
     public function isAdmin ($iGroupProfileId, $iFanId, $aDataEntry = array())
     {
-        if (isset($aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']]) && $iFanId == $aDataEntry[$this->_oConfig->CNF['FIELD_AUTHOR']])
+        $CNF = &$this->_oConfig->CNF;
+
+        if(isset($aDataEntry[$CNF['FIELD_AUTHOR']]) && $iFanId == $aDataEntry[$CNF['FIELD_AUTHOR']])
             return true;
-        if (!isset($this->_oConfig->CNF['TABLE_ADMINS']))
+
+        if(!isset($CNF['TABLE_ADMINS']))
             return false;
-        $sQuery = $this->prepare("SELECT `id` FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = ? AND `fan_id` = ?", $iGroupProfileId, $iFanId);
-        return $this->getOne($sQuery) ? true : false;
+
+        $aBindings = array('group_profile_id' => $iGroupProfileId, 'fan_id' => $iFanId);
+        $sWhereClause = " AND `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id";
+        if($this->_oConfig->isPaidJoin()) {
+            $aBindings['role'] = 'BX_BASE_MOD_GROUPS_ROLE_COMMON';
+            $sWhereClause .= " AND `role` <> :role";
+        }
+
+        return $this->getOne("SELECT `id` FROM `" . $CNF['TABLE_ADMINS'] . "` WHERE 1" . $sWhereClause, $aBindings) ? true : false;
     }
 
     public function getAdmin ($iGroupProfileId, $iProfileId, $bRoleOnly = false)
     {
+        $CNF = &$this->_oConfig->CNF;
+
         $sMethod = 'getRow';
         $sSelectClause = '*';
 
@@ -106,45 +118,230 @@ class BxBaseModGroupsDb extends BxBaseModProfileDb
             $sSelectClause = '`role`';
         }
 
-        return $this->$sMethod("SELECT " . $sSelectClause . " FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id LIMIT 1", array(
-            'group_profile_id' => $iGroupProfileId,
-            'fan_id' => $iProfileId
-        ));
+        $aBindings = array('group_profile_id' => $iGroupProfileId, 'fan_id' => $iProfileId);
+        $sWhereClause = " AND `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id";
+        if($this->_oConfig->isPaidJoin()) {
+            $aBindings['role'] = 'BX_BASE_MOD_GROUPS_ROLE_COMMON';
+            $sWhereClause .= " AND `role` <> :role";
+        }
+
+        return $this->$sMethod("SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_ADMINS'] . "` WHERE 1" . $sWhereClause . " LIMIT 1", $aBindings);
     }
 
     public function getAdmins ($iGroupProfileId, $iStart = 0, $iLimit = 0)
     {
-        if ($iLimit > 0)
-            $sQuery = $this->prepare("SELECT `fan_id` FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = ? LIMIT ?, ?", $iGroupProfileId, $iStart, $iLimit);
-        else
-            $sQuery = $this->prepare("SELECT `fan_id` FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = ?", $iGroupProfileId);
+        $CNF = &$this->_oConfig->CNF;
+
+        $aBindings = array('group_profile_id' => $iGroupProfileId);
+        $sWhereClause = " AND `group_profile_id` = :group_profile_id";
+        if($this->_oConfig->isPaidJoin()) {
+            $aBindings['role'] = 'BX_BASE_MOD_GROUPS_ROLE_COMMON';
+            $sWhereClause .= " AND `role` <> :role";
+        }
+
+        $sLimitClause = "";
+        if($iLimit > 0)
+            $sLimitClause = " LIMIT " . (int)$iStart . ", " . (int)$iLimit;
+
+        return $this->getColumn("SELECT `fan_id` FROM `" . $CNF['TABLE_ADMINS'] . "` WHERE 1" . $sWhereClause . $sLimitClause, $aBindings);
+    }
+
+    public function getRole($iGroupProfileId, $iProfileId)
+    {
+        return (int)$this->getRoles(array('type' => 'by_gf_id', 'group_profile_id' => $iGroupProfileId, 'fan_id' => $iProfileId, 'role_only' => true));
+    }
+
+    public function getRoles($aParams)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`ta`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['value']
+                );
+
+                $sWhereClause = "AND `ta`.`id`=:id";
+                break;
+
+            case 'by_gf_id':
+                $aMethod['name'] = 'getRow';
+            	$aMethod['params'][1] = array(
+                    'group_profile_id' => $aParams['group_profile_id'],
+                    'fan_id' => $aParams['fan_id'],
+                );
+
+                $sWhereClause = "AND `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id";
+                $sLimitClause = 1;
+
+                if(!empty($aParams['role_only'])) {
+                    $aMethod['name'] = 'getOne';
+                    $sSelectClause = "`ta`.`role`";
+                }
+                break;
+
+            case 'expired':
+                $sWhereClause .= "AND `ta`.`added` < UNIX_TIMESTAMP() AND `ta`.`expired` <> 0 AND `ta`.`expired` < UNIX_TIMESTAMP()";
+                break;
+        }
+
+        if(!empty($sOrderClause))
+            $sOrderClause = "ORDER BY " . $sOrderClause;
+        
+        if(!empty($sLimitClause))
+            $sLimitClause = "LIMIT " . $sLimitClause;
+
+        $sSql = "SELECT {select} FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` AS `ta` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " {order} {limit}";
+
+        $aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array($sSelectClause, $sOrderClause, $sLimitClause), $sSql);
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function updateRoles($aSet, $aWhere)
+    {
+        $sWhereClause = 1;
+        if(!empty($aWhere))
+            $sWhereClause = $this->arrayToSQL($aWhere, ' AND ');
+
+        return (int)$this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` SET " . $this->arrayToSQL($aSet) . " WHERE " . $sWhereClause) > 0;
+    }
+    
+    public function deleteRoles($aWhere)
+    {
+    	if(empty($aWhere))
+            return false;
 
-        return $this->getColumn($sQuery);
+        return (int)$this->query("DELETE FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE " . $this->arrayToSQL($aWhere, ' AND ')) > 0;
     }
 
-    public function getRole ($iGroupProfileId, $iProfileId)
+    public function setRole ($iGroupProfileId, $iProfileId, $iRole, $mixedPeriod = false, $sOrder = '')
     {
-        return (int)$this->getOne("SELECT `role` FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id LIMIT 1", array(
+        $CNF = &$this->_oConfig->CNF;
+
+        $iNow = time();
+
+        $aBindings = array(
             'group_profile_id' => $iGroupProfileId,
-            'fan_id' => $iProfileId
-        ));
+            'fan_id' => $iProfileId,
+            'role' => $iRole,
+            'order' => $sOrder,
+            'added' => $iNow
+        );
+        $sSetClause = "`group_profile_id` = :group_profile_id, `fan_id` = :fan_id, `role` = :role, `added` = :added";
+
+        $sSetExpired = ""; 
+        if(!empty($mixedPeriod)) {
+            if(is_numeric($mixedPeriod))
+        	$mixedPeriod = array('period' => (int)$mixedPeriod, 'period_unit' => BX_BASE_MOD_GROUPS_PERIOD_UNIT_DAY);
+
+            $aBindings['period'] = (int)$mixedPeriod['period'];
+
+            switch($mixedPeriod['period_unit']) {
+                case BX_BASE_MOD_GROUPS_PERIOD_UNIT_DAY:
+                case BX_BASE_MOD_GROUPS_PERIOD_UNIT_WEEK:
+                    if($mixedPeriod['period_unit'] == BX_BASE_MOD_GROUPS_PERIOD_UNIT_WEEK)
+                        $aBindings['period'] *= 7;
+
+                    $sSetExpired = "DATE_ADD(FROM_UNIXTIME(:added), INTERVAL :period DAY)";
+                    break;
+
+                case BX_BASE_MOD_GROUPS_PERIOD_UNIT_MONTH:
+                    $sSetExpired = "DATE_ADD(FROM_UNIXTIME(:added), INTERVAL :period MONTH)";
+                    break;
+
+                case BX_BASE_MOD_GROUPS_PERIOD_UNIT_YEAR:
+                    $sSetExpired = "DATE_ADD(FROM_UNIXTIME(:added), INTERVAL :period YEAR)";
+                    break;
+            }
+
+            if(!empty($sSetExpired) && !empty($mixedPeriod['period_reserve'])) {
+                $aBindings['reserve'] = (int)$mixedPeriod['period_reserve'];
+
+                $sSetExpired = "DATE_ADD(" . $sSetExpired . ", INTERVAL :reserve DAY)";
+            }
+
+            if(!empty($sSetExpired))
+                $sSetClause .= ", `expired` = UNIX_TIMESTAMP(" . $sSetExpired . ")";
+        }
+
+        return !$this->query("REPLACE INTO `" . $CNF['TABLE_ADMINS'] . "` SET " . $sSetClause, $aBindings) ? false : true;
     }
 
-    public function setRole ($iGroupProfileId, $iProfileId, $iRole)
+    public function unsetRole($iGroupProfileId, $iProfileId)
     {
-        if($iRole == 0)
-            $mixedResult = $this->query("DELETE FROM `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` WHERE `group_profile_id` = :group_profile_id AND `fan_id` = :fan_id", array(
-                'group_profile_id' => $iGroupProfileId,
-                'fan_id' => $iProfileId,
-            ));
-        else
-            $mixedResult = $this->query("REPLACE INTO `" . $this->_oConfig->CNF['TABLE_ADMINS'] . "` SET `group_profile_id` = :group_profile_id, `fan_id` = :fan_id, `role` = :role", array(
-                'group_profile_id' => $iGroupProfileId,
-                'fan_id' => $iProfileId,
-                'role' => $iRole
-            ));
+        return $this->deleteRoles(array('group_profile_id' => $iGroupProfileId, 'fan_id' => $iProfileId));
+    }
 
-        return !$mixedResult ? false : true;
+    public function getPrices($aParams)
+    {
+        $aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`tp`.*";
+        $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
+
+        if(!isset($aParams['order']) || empty($aParams['order']))
+            $sOrderClause = "ORDER BY `tp`.`Order` ASC";
+
+        switch($aParams['type']) {
+            case 'by_id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['value']
+                );
+
+                $sWhereClause .= "AND `tp`.`id`=:id";
+                break;
+
+            case 'by_name':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['value']
+                );
+
+                $sWhereClause .= "AND `tp`.`name`=:name";
+                break;
+
+            case 'by_profile_id':
+            	$aMethod['params'][1] = array(
+                    'profile_id' => $aParams['profile_id']
+                );
+
+                $sWhereClause .= "AND `tp`.`profile_id`=:profile_id";
+                break;
+
+            case 'by_prpp':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'profile_id' => $aParams['profile_id'],
+                    'role_id' => $aParams['role_id'],
+                    'period' => $aParams['period'],
+                    'period_unit' => $aParams['period_unit'],
+                );
+
+                $sWhereClause .= "AND `tp`.`profile_id`=:profile_id AND `tp`.`role_id`=:role_id AND `tp`.`period`=:period AND `tp`.`period_unit`=:period_unit";
+                break;
+        }
+
+        $sSql = "SELECT {select} FROM `" . $this->_oConfig->CNF['TABLE_PRICES'] . "` AS `tp` " . $sJoinClause . " WHERE 1 " . $sWhereClause . " {order} {limit}";
+
+        $aMethod['params'][0] = str_replace(array('{select}', '{order}', '{limit}'), array($sSelectClause, $sOrderClause, $sLimitClause), $sSql);
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getPriceOrderMax($iRoleId)
+    {
+        return (int)$this->getOne("SELECT MAX(`order`) FROM `" . $this->_oConfig->CNF['TABLE_PRICES'] . "` WHERE `role_id`=:role_id", array(
+            'role_id' => $iRoleId
+        ));
+    }
+
+    public function deletePrices($aWhere)
+    {
+        return (int)$this->query("DELETE FROM `" . $this->_oConfig->CNF['TABLE_PRICES'] . "` WHERE " . $this->arrayToSQL($aWhere, " AND ")) > 0;
     }
 
     public function insertInvite($sKey, $sGroupProfileId, $iAuthorProfileId, $iInvitedProfileId)

+ 69 - 8
modules/base/groups/classes/BxBaseModGroupsGridConnections.php

@@ -20,10 +20,17 @@ class BxBaseModGroupsGridConnections extends BxDolGridConnections
     protected $_iGroupProfileId;
     protected $_aContentInfo = array();
 
+    protected $_bPaidJoin;
+    protected $_bMember;
+    protected $_bManageMembers;
+
     public function __construct ($aOptions, $oTemplate = false)
     {
         $this->_oModule = BxDolModule::getInstance($this->_sContentModule);
-        $this->_sObjectConnections = $this->_oModule->_oConfig->CNF['OBJECT_CONNECTIONS'];
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_sObjectConnections = $CNF['OBJECT_CONNECTIONS'];
 
         parent::__construct ($aOptions, $oTemplate);
         if(!$this->_bInit) 
@@ -38,6 +45,11 @@ class BxBaseModGroupsGridConnections extends BxDolGridConnections
         if($this->_oModule->checkAllowedEdit($this->_aContentInfo) === CHECK_ACTION_RESULT_ALLOWED || $this->_iGroupProfileId == bx_get_logged_profile_id())
             $this->_bOwner = true;
 
+        $this->_bPaidJoin = $this->_oModule->isPaidJoinByProfile($this->_iGroupProfileId);
+
+        $this->_bMember = $this->_oModule->isFan($this->_aContentInfo[$CNF['FIELD_ID']]);
+        $this->_bManageMembers = $this->_oModule->checkAllowedManageFans($this->_iGroupProfileId) === CHECK_ACTION_RESULT_ALLOWED || $this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId) === CHECK_ACTION_RESULT_ALLOWED;
+
         $aSQLParts = $this->_oConnection->getConnectedInitiatorsAsSQLParts('p', 'id', $this->_iGroupProfileId, $this->_bOwner ? false : true);
         $this->addMarkers(array(
             'profile_id' => $this->_iGroupProfileId,
@@ -65,8 +77,8 @@ class BxBaseModGroupsGridConnections extends BxDolGridConnections
         if(!$this->_bRoles)
             return echoJson(array());
 
-        if($this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId) !== CHECK_ACTION_RESULT_ALLOWED)
-            return echoJson(array('msg' => _t('_sys_txt_access_denied')));
+        if(($mixedResult = $this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return echoJson(array('msg' => $mixedResult));
 
         list($iId, $iViewedId) = $this->_prepareIds();
         if(!$iId)
@@ -105,17 +117,16 @@ class BxBaseModGroupsGridConnections extends BxDolGridConnections
         if(!$this->_bRoles)
             return echoJson(array());
 
-        if($this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId) !== CHECK_ACTION_RESULT_ALLOWED)
-            return echoJson(array('msg' => _t('_sys_txt_access_denied')));
+        if(($mixedResult = $this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
+            return echoJson(array('msg' => $mixedResult));
 
         list($iId, $iViewedId) = $this->_prepareIds();
 
         if(!$iId || bx_get('role') === false)
             return echoJson(array('msg' => _t('_sys_txt_error_occured')));
 
-        $aResult = $this->_oModule->setRole($this->_iGroupProfileId, $iId, (int)bx_get('role'));
-        if($aResult['code'] != 0)
-            return echoJson(array('msg' => $aResult['msg']));
+        if(!$this->_oModule->setRole($this->_iGroupProfileId, $iId, (int)bx_get('role')))
+            return echoJson(array('msg' => _t('_error occured')));
 
         echoJson(array('grid' => $this->getCode(false), 'blink' => $iId));
     }
@@ -131,6 +142,56 @@ class BxBaseModGroupsGridConnections extends BxDolGridConnections
         return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
     }
 
+    protected function _getCellHeaderRoleAdded($sKey, $aField)
+    {
+        if(!$this->_bPaidJoin || !($this->_bMember || $this->_bManageMembers))
+            return '';
+
+        return parent::_getCellHeaderDefault ($sKey, $aField);
+    }
+
+    protected function _getCellRoleAdded($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = '';
+
+        if(!$this->_bPaidJoin || !($this->_bMember || $this->_bManageMembers))
+            return $mixedValue;
+
+        $iProfileId = (int)$aRow[$this->_aOptions['field_id']];
+        if($this->_bManageMembers || $iProfileId == bx_get_logged_profile_id()) {
+            $aRole = $this->_oModule->_oDb->getRoles(array('type' => 'by_gf_id', 'group_profile_id' => $this->_iGroupProfileId, 'fan_id' => $iProfileId));
+            if(!empty($aRole) && is_array($aRole))
+                $mixedValue = bx_time_js($aRole['added'], BX_FORMAT_DATE, true);
+        }
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellHeaderRoleExpired($sKey, $aField)
+    {
+        if(!$this->_bPaidJoin || !($this->_bMember || $this->_bManageMembers))
+            return '';
+        
+        return parent::_getCellHeaderDefault ($sKey, $aField);
+    }
+
+    protected function _getCellRoleExpired($mixedValue, $sKey, $aField, $aRow)
+    {
+        $mixedValue = '';
+
+        if(!$this->_bPaidJoin || !($this->_bMember || $this->_bManageMembers))
+            return '';
+
+        $iProfileId = (int)$aRow[$this->_aOptions['field_id']];
+        if($this->_bManageMembers || $iProfileId == bx_get_logged_profile_id()) {
+            $aRole = $this->_oModule->_oDb->getRoles(array('type' => 'by_gf_id', 'group_profile_id' => $this->_iGroupProfileId, 'fan_id' => $iProfileId));
+            if(!empty($aRole) && is_array($aRole))
+                $mixedValue = bx_time_js($aRole['expired'], BX_FORMAT_DATE, true);
+        }
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
     protected function _getActionSetRole ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
     {
         if ($this->_oModule->checkAllowedManageAdmins($this->_iGroupProfileId) !== CHECK_ACTION_RESULT_ALLOWED)

+ 97 - 0
modules/base/groups/classes/BxBaseModGroupsGridPrices.php

@@ -0,0 +1,97 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+
+class BxBaseModGroupsGridPrices extends BxTemplGrid
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    protected $_sParamsDivider = '#-#';
+
+    protected $_iGroupProfileId;
+    protected $_iGroupContentId;
+    protected $_aGroupContentInfo;
+
+    protected $_aRoles;
+    protected $_aPeriodUnits;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = $this->_oModule->_oConfig->CNF;
+
+        $this->_aRoles = BxDolFormQuery::getDataItems($CNF['OBJECT_PRE_LIST_ROLES']);
+        $this->_aPeriodUnits = BxDolForm::getDataItems($CNF['OBJECT_FORM_PRELISTS_PERIOD_UNITS']);
+
+        $this->_iGroupProfileId = 0;
+        $this->_iGroupContentId = 0;
+        $this->_aGroupContentInfo = array();
+        if(($iGroupProfileId = bx_get('profile_id')) !== false)
+            $this->setProfileId($iGroupProfileId);
+    }
+
+    public function setProfileId($iProfileId)
+    {
+        $this->_iGroupProfileId = (int)$iProfileId;
+        $this->_aQueryAppend['profile_id'] = $this->_iGroupProfileId;
+
+        $oGroupProfile = BxDolProfile::getInstance($this->_iGroupProfileId);
+        if($oGroupProfile) {
+            $this->_iGroupContentId = (int)$oGroupProfile->getContentId();
+            $this->_aGroupContentInfo = $this->_oModule->_oDb->getContentInfoById($this->_iGroupContentId);
+        }
+    }
+
+    protected function _getCellPeriod($mixedValue, $sKey, $aField, $aRow)
+    {
+        $CNF = $this->_oModule->_oConfig->CNF;
+
+        if((int)$mixedValue == 0)
+            $mixedValue = _t('_lifetime');
+        else
+            $mixedValue = _t($CNF['T']['txt_n_unit'], $mixedValue, _t($this->_aPeriodUnits[$aRow['period_unit']]));
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellPrice($mixedValue, $sKey, $aField, $aRow)
+    {
+        if((float)$mixedValue != 0) {
+            $aCurrency = $this->_oModule->_oConfig->getCurrency();
+
+            $mixedValue = $aCurrency['sign'] . $mixedValue;
+        }
+        else 
+            $mixedValue = _t('_free');
+
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getIds()
+    {
+        $aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds)) {
+            $iId = (int)bx_get('id');
+            if(!$iId) 
+                return false;
+
+            $aIds = array($iId);
+        }
+
+        return $aIds;
+    }
+}
+
+/** @} */

+ 220 - 0
modules/base/groups/classes/BxBaseModGroupsGridPricesManage.php

@@ -0,0 +1,220 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+require_once('BxBaseModGroupsGridPrices.php');
+
+class BxBaseModGroupsGridPricesManage extends BxBaseModGroupsGridPrices
+{
+    protected $_iRoleId;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_iRoleId = 0;
+        if(($iRoleId = (int)bx_get('role_id')) !== false)
+            $this->setRoleId($iRoleId);
+    }
+
+    public function setRoleId($iRoleId)
+    {
+        $this->_iRoleId = (int)$iRoleId;
+        $this->_aQueryAppend['role_id'] = $this->_iRoleId;
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+        $sResult = parent::getCode($isDisplayHeader);
+        if(empty($sResult))
+            return $sResult;
+
+        $sJsCode = $this->_oModule->_oTemplate->getJsCode('prices', array(
+            'sObjNameGrid' => $this->_sObject, 
+            'aHtmlIds' => $this->_oModule->_oConfig->getHtmlIds()
+        ));
+
+        return $sJsCode . $sResult;
+    }
+
+    public function performActionAdd()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$sAction = 'add';
+
+        $sFilter = bx_get('filter');
+        if(strpos($sFilter, $this->_sParamsDivider) !== false)
+            list($this->_iRoleId, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+
+        if(!is_numeric($this->_iRoleId) && ($iRoleId = bx_get('role_id')) !== false)
+            $this->_iRoleId = (int)$iRoleId;
+
+    	$oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_PRICE'], $CNF['OBJECT_FORM_PRICE_DISPLAY_ADD']);
+    	$oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . bx_append_url_params('grid.php', array('o' => $this->_sObject, 'a' => $sAction, 'profile_id' => $this->_iGroupProfileId, 'role_id' => $this->_iRoleId));
+    	$oForm->aInputs['role_id']['value'] = $this->_iRoleId;
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+            $iPeriod = $oForm->getCleanValue('period');
+            $sPeriodUnit = $oForm->getCleanValue('period_unit');
+
+            if(!empty($iPeriod) && empty($sPeriodUnit)) 
+                return echoJson(array('msg' => _t('_bx_groups_form_price_input_err_period_unit')));
+
+            $aPrice = $this->_oModule->_oDb->getPrices(array('type' => 'by_prpp', 'profile_id' => $this->_iGroupProfileId, 'role_id' => $this->_iRoleId, 'period' => $iPeriod, 'period_unit' => $sPeriodUnit));
+            if(!empty($aPrice) && is_array($aPrice))
+                return echoJson(array('msg' => _t('_bx_groups_err_price_duplicate')));
+
+            $sName = uriGenerate(strtolower(strmaxtextlen($this->_aGroupContentInfo[$CNF['FIELD_TITLE']], 8, '')) . ' ' . strtolower($this->_aRoles[$this->_iRoleId]) . ' ' . $iPeriod . ' ' . $sPeriodUnit, $CNF['TABLE_PRICES'], 'name');
+
+            $iId = (int)$oForm->insert(array('name' => $sName, 'profile_id' => $this->_iGroupProfileId, 'order' => $this->_oModule->_oDb->getPriceOrderMax($this->_iRoleId) + 1));
+            if($iId != 0)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $iId);
+            else
+                $aRes = array('msg' => _t('_bx_groups_err_cannot_perform'));
+
+            echoJson($aRes);
+            return;
+        }
+
+        bx_import('BxTemplFunctions');
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('popup_price'), _t('_bx_groups_popup_title_price_add'), $this->_oModule->_oTemplate->parseHtmlByName('popup_price.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        )));
+
+        echoJson(array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false))));
+    }
+
+    public function performActionEdit()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+        $sAction = 'edit';
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return echoJson(array());
+
+        $aItem = $this->_oModule->_oDb->getPrices(array('type' => 'by_id', 'value' => array_shift($aIds)));
+        if(!is_array($aItem) || empty($aItem))
+            return echoJson(array());
+
+        $oForm = BxDolForm::getObjectInstance($CNF['OBJECT_FORM_PRICE'], $CNF['OBJECT_FORM_PRICE_DISPLAY_EDIT']);
+    	$oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . bx_append_url_params('grid.php', array('o' => $this->_sObject, 'a' => $sAction, 'profile_id' => $this->_iGroupProfileId, 'role_id' => $this->_iRoleId));
+
+        $oForm->initChecker($aItem);
+        if($oForm->isSubmittedAndValid()) {
+            if($oForm->update($aItem['id']) !== false)
+                $aRes = array('grid' => $this->getCode(false), 'blink' => $aItem['id']);
+            else
+                $aRes = array('msg' => _t('_bx_groups_err_cannot_perform'));
+
+            return echoJson($aRes);
+        }
+
+        bx_import('BxTemplFunctions');
+        $sContent = BxTemplFunctions::getInstance()->popupBox($this->_oModule->_oConfig->getHtmlIds('popup_price'), _t('_bx_groups_popup_title_price_edit'), $this->_oModule->_oTemplate->parseHtmlByName('popup_price.html', array(
+            'form_id' => $oForm->aFormAttrs['id'],
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        )));
+
+        $aRes = array('popup' => array('html' => $sContent, 'options' => array('closeOnOuterClick' => false)));
+
+        return echoJson($aRes);
+    }
+
+    public function performActionDelete()
+    {
+    	$CNF = &$this->_oModule->_oConfig->CNF;
+
+    	$aIds = bx_get('ids');
+        if(!$aIds || !is_array($aIds))
+            return echoJson(array());
+
+        $iAffected = 0;
+        $aIdsAffected = array();
+        foreach($aIds as $iId)
+            if($this->_oModule->_oDb->deletePrices(array('id' => $iId))) {
+                $aIdsAffected[] = $iId;
+                $iAffected++;
+            }
+
+        return echoJson($iAffected ? array('grid' => $this->getCode(false), 'blink' => $aIdsAffected) : array('msg' => _t('_bx_groups_err_cannot_perform')));
+    }
+
+    protected function _addJsCss()
+    {
+        parent::_addJsCss();
+
+        $this->_oModule->_oTemplate->addJs(array('jquery.form.min.js', 'prices.js'));
+        $this->_oModule->_oTemplate->addCss(array('prices.css'));
+    }
+
+    protected function _getFilterControls()
+    {
+        parent::_getFilterControls();
+
+        $sContent = '';
+        $oForm = new BxTemplFormView(array());
+
+        $aInputRoles = array(
+            'type' => 'select',
+            'name' => 'role',
+            'attrs' => array(
+                'id' => 'bx-grid-level-' . $this->_sObject,
+                'onChange' => 'javascript:' . $this->_oModule->_oConfig->getJsObject('prices') . '.onChangeRole()'
+            ),
+            'value' => $this->_iRoleId,
+            'values' => $this->_aRoles
+        );
+        $sContent .=  $oForm->genRow($aInputRoles);
+
+        $aInputSearch = array(
+            'type' => 'text',
+            'name' => 'keyword',
+            'attrs' => array(
+                'id' => 'bx-grid-search-' . $this->_sObject,
+            ),
+        );
+        $sContent .= $oForm->genRow($aInputSearch);
+
+        return $sContent;
+    }
+
+    protected function _getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage)
+    {
+        if(strpos($sFilter, $this->_sParamsDivider) !== false) {
+            list($iRoleId, $sFilter) = explode($this->_sParamsDivider, $sFilter);
+            if(!is_numeric($iRoleId))
+                $iRoleId = 0;
+
+            $this->setRoleId($iRoleId);
+        }
+
+        $this->_aOptions['source'] .= $this->_oModule->_oDb->prepareAsString("AND `profile_id`=? AND `role_id`=? ", $this->_iGroupProfileId, $this->_iRoleId);
+
+        return parent::_getDataSql($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);;
+    }
+
+    protected function _isVisibleGrid ($a)
+    {
+        return $this->_oModule->checkAllowedManageAdmins($this->_aGroupContentInfo) == CHECK_ACTION_RESULT_ALLOWED;
+    }
+}
+
+/** @} */

+ 120 - 0
modules/base/groups/classes/BxBaseModGroupsGridPricesView.php

@@ -0,0 +1,120 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    BaseGroups Base classes for groups modules
+ * @ingroup     UnaModules
+ * 
+ * @{
+ */
+
+require_once('BxBaseModGroupsGridPrices.php');
+
+class BxBaseModGroupsGridPricesView extends BxBaseModGroupsGridPrices
+{
+    protected $_oPayment;
+    protected $_bTypeSingle;
+    protected $_bTypeRecurring;
+
+    protected $_iSeller;
+    protected $_iClient;
+    protected $_iClientRole;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $this->_oPayment = BxDolPayments::getInstance();
+
+        $this->_iSeller = $this->_aGroupContentInfo[$CNF['FIELD_AUTHOR']];
+        $this->_iClient = bx_get_logged_profile_id();
+        $this->_iClientRole = $this->_oModule->getRole($this->_iGroupProfileId, $this->_iClient);
+
+        $this->_bTypeSingle = $this->_oPayment->isAcceptingPayments($this->_iSeller, BX_PAYMENT_TYPE_SINGLE);
+        $this->_bTypeRecurring = $this->_oPayment->isAcceptingPayments($this->_iSeller, BX_PAYMENT_TYPE_RECURRING);
+    }
+
+    public function performActionChoose()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aIds = $this->_getIds();
+        if($aIds === false)
+            return echoJson(array());
+
+        $aItem = $this->_oModule->_oDb->getPrices(array('type' => 'by_id', 'value' => $aIds[0]));
+        if(!is_array($aItem) || empty($aItem) || (float)$aItem['price'] != 0)
+            return echoJson(array());
+
+        $aResult = array();
+        if($this->_oModule->setRole($this->_iGroupProfileId, $this->_iClient, $aItem['role_id'], array('period' => $aItem['period'], 'period_unit' => $aItem['period_unit'])))
+            $aResult = array('grid' => $this->getCode(false), 'blink' => $aItem['id'], 'msg' => _t($CNF['T']['msg_performed']));
+        else
+            $aResult = array('msg' => _t($CNF['T']['err_cannot_perform']));
+
+        return echoJson($aResult);
+    }
+
+    public function getCode($isDisplayHeader = true)
+    {
+    	return $this->_oPayment->getCartJs() . parent::getCode($isDisplayHeader);
+    }
+
+    protected function _getCellRoleId($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($this->_aRoles[$mixedValue], $sKey, $aField, $aRow);
+    }
+
+    protected function _getActionChoose ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        if((float)$aRow['price'] != 0 || $this->_oModule->getRole($this->_iGroupProfileId, $this->_iClient) === (int)$aRow['role_id'])
+            return '';
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionBuy ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if((float)$aRow['price'] == 0 || !$this->_bTypeSingle || $this->_bTypeRecurring)
+            return '';
+
+        $aJs = $this->_oPayment->getAddToCartJs($this->_iSeller, $this->_sModule, $aRow['id'], 1, true);
+        if(!empty($aJs) && is_array($aJs)) {
+            list($sJsCode, $sJsMethod) = $aJs;
+
+            $a['attr'] = array(
+                'title' => bx_html_attribute(_t($CNF['T']['txt_buy_title'])),
+                'onclick' => $sJsMethod
+            );
+        }
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+
+    protected function _getActionSubscribe ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if((float)$aRow['price'] == 0 || !$this->_bTypeRecurring)
+            return '';
+
+        $aJs = $this->_oPayment->getSubscribeJs($this->_iSeller, '', $this->_sModule, $aRow['id'], 1);
+        if(!empty($aJs) && is_array($aJs)) {
+            list($sJsCode, $sJsMethod) = $aJs;
+
+            $a['attr'] = array(
+                'title' => bx_html_attribute(_t($CNF['T']['txt_subscribe_title'])),
+                'onclick' => $sJsMethod
+            );
+        }
+
+        return  parent::_getActionDefault($sType, $sKey, $a, false, $isDisabled, $aRow);
+    }
+}
+
+/** @} */

+ 23 - 0
modules/base/groups/classes/BxBaseModGroupsMenuSnippetMeta.php

@@ -26,6 +26,29 @@ class BxBaseModGroupsMenuSnippetMeta extends BxBaseModProfileMenuSnippetMeta
         }
     }
 
+    protected function _getMenuItemJoinPaid($aItem)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        if(!isLogged() || $this->_oModule->checkAllowedFanAdd($this->_aContentInfo) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        if(!$oConnection)
+            return false;
+        
+        $sTitle = $this->_getMenuItemConnectionsTitle('add', $oConnection);
+        if(empty($sTitle))
+            return false;
+
+        return $this->getUnitMetaItemButtonSmall(_t('_bx_groups_menu_item_title_pay_and_join'), array(
+            'href' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php', array(
+                'i' => $CNF['URI_JOIN_ENTRY'],
+                'profile_id' => $this->_oContentProfile->id()
+            ))
+        ));
+    }
+
     protected function _getMenuItemJoin($aItem)
     {
         if (isset($this->_oModule->_oConfig->CNF['OBJECT_CONNECTIONS']))

+ 5 - 0
modules/base/groups/classes/BxBaseModGroupsMenuViewActionsAll.php

@@ -24,6 +24,11 @@ class BxBaseModGroupsMenuViewActionsAll extends BxBaseModProfileMenuViewActionsA
         return $this->_getMenuItemByNameActions($aItem);
     }
 
+    protected function _getMenuItemProfileFanAddPaid($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
     protected function _getMenuItemProfileFanRemove($aItem)
     {
         return $this->_getMenuItemByNameActionsMore($aItem);

+ 375 - 17
modules/base/groups/classes/BxBaseModGroupsModule.php

@@ -9,6 +9,7 @@
  * @{
  */
 
+define('BX_BASE_MOD_GROUPS_ROLE_COMMON', 0);
 define('BX_BASE_MOD_GROUPS_ROLE_ADMINISTRATOR', 1);
 define('BX_BASE_MOD_GROUPS_ROLE_MODERATOR', 2);
 
@@ -19,6 +20,11 @@ define('BX_BASE_MOD_GROUPS_ACTION_MANAGE_FANS', 'manage_fans');
 define('BX_BASE_MOD_GROUPS_ACTION_MANAGE_ROLES', 'manage_roles');
 define('BX_BASE_MOD_GROUPS_ACTION_DELETE', 'delete');
 
+define('BX_BASE_MOD_GROUPS_PERIOD_UNIT_DAY', 'day');
+define('BX_BASE_MOD_GROUPS_PERIOD_UNIT_WEEK', 'week');
+define('BX_BASE_MOD_GROUPS_PERIOD_UNIT_MONTH', 'month');
+define('BX_BASE_MOD_GROUPS_PERIOD_UNIT_YEAR', 'year');
+
 /**
  * Groups profiles module.
  */
@@ -438,6 +444,47 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         return $this->_serviceBrowse ('created_entries', array('author' => $iProfileId), BX_DB_PADDING_DEF, $bDisplayEmptyMsg);
     }
     
+    public function serviceEntityPricing($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return '';
+
+        if(!$this->_oConfig->isPaidJoin())
+            return '';
+
+        if($this->checkAllowedManageAdmins($iProfileId) !== CHECK_ACTION_RESULT_ALLOWED)
+            return MsgBox(_t('_Access denied'));
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_PRICES_MANAGE']);
+        if(!$oGrid)
+            return '';
+
+        return $oGrid->getCode();
+    }
+
+    public function serviceEntityJoin($iProfileId = 0)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$iProfileId)
+            $iProfileId = bx_process_input(bx_get('profile_id'), BX_DATA_INT);
+        if(!$iProfileId)
+            return '';
+
+        if(!$this->_oConfig->isPaidJoin())
+            return '';
+
+        $oGrid = BxDolGrid::getObjectInstance($CNF['OBJECT_GRID_PRICES_VIEW']);
+        if(!$oGrid)
+            return '';
+
+        return $oGrid->getCode();
+    }
+    
     public function serviceEntityInvite ($iContentId = 0, $bErrorMsg = true)
     {
         if (isset($this->_oConfig->CNF['OBJECT_FORM_ENTRY_DISPLAY_INVITE']))
@@ -471,7 +518,242 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         ));
     }
 
-	/**
+    public function serviceIsPricingAvaliable($iProfileId)
+    {
+        if(!$this->_oConfig->isPaidJoin())
+            return false;
+
+        if($this->checkAllowedManageAdmins($iProfileId) !== CHECK_ACTION_RESULT_ALLOWED)
+            return false;
+
+        return true;
+    }
+
+    public function serviceIsPaidJoinAvaliable($iProfileId)
+    {
+        return $this->isPaidJoinByProfile($iProfileId);
+    }
+
+    public function serviceIsPaidJoinAvaliableByContent($iContentId)
+    {
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if(!$oProfile)
+            return false;
+
+        return $this->isPaidJoinByProfile($oProfile->id());
+    }
+
+    public function serviceIsFreeJoinAvaliable($iProfileId)
+    {
+        return !$this->isPaidJoinByProfile($iProfileId);
+    }
+
+    public function serviceIsFreeJoinAvaliableByContent($iContentId)
+    {
+        $oProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
+        if(!$oProfile)
+            return false;
+
+        return !$this->isPaidJoinByProfile($oProfile->id());
+    }
+
+    public function isPaidJoinByProfile($iProfileId)
+    {
+        if(!$this->_oConfig->isPaidJoin())
+            return false;
+
+        $aPrices = $this->_oDb->getPrices(array('type' => 'by_profile_id', 'profile_id' => $iProfileId));
+        if(empty($aPrices) || !is_array($aPrices))
+            return false;
+
+        return true;
+    }
+
+    /**
+     * Integration with Payments.
+     */
+    public function serviceGetPaymentData()
+    {
+        return $this->_aModule;
+    }
+
+    public function serviceGetCartItem($mixedItemId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!$mixedItemId)
+            return array();
+
+        if(is_numeric($mixedItemId))
+            $aItem = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => (int)$mixedItemId));
+        else 
+            $aItem = $this->_oDb->getPrices(array('type' => 'by_name', 'value' => $mixedItemId));
+
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        if(!$this->isPaidJoinByProfile($aItem['profile_id']))
+            return array();
+
+        $oGroupProfile = BxDolProfile::getInstance($aItem['profile_id']);
+        if(!$oGroupProfile)
+            return array();
+
+        $aGroupProfile = $this->_oDb->getContentInfoById($oGroupProfile->getContentId());
+        
+        $aRoles = BxDolFormQuery::getDataItems($CNF['OBJECT_PRE_LIST_ROLES']);
+
+        return array (
+            'id' => $aItem['id'],
+            'author_id' => $aGroupProfile[$CNF['FIELD_AUTHOR']],
+            'name' => $aItem['name'],
+            'title' => _t($CNF['T']['txt_cart_item_title'], $oGroupProfile->getDisplayName(), $aRoles[$aItem['role_id']], $aItem['period'], $aItem['period_unit']),
+            'description' => '',
+            'url' => $oGroupProfile->getUrl(),
+            'price_single' => $aItem['price'],
+            'price_recurring' => $aItem['price'],
+            'period_recurring' => $aItem['period'],
+            'period_unit_recurring' => $aItem['period_unit']
+        );
+    }
+
+    public function serviceGetCartItems($iSellerId)
+    {
+    	$CNF = &$this->_oConfig->CNF;
+
+    	if(empty($iSellerId))
+    	    return array();
+
+        $sModule = $this->getName();
+        $aRoles = BxDolFormQuery::getDataItems($CNF['OBJECT_PRE_LIST_ROLES']);
+
+        $aGroups = $this->_oDb->getEntriesBy(array('type' => 'author', 'author' => $iSellerId));
+
+        $aResult = array();
+        foreach($aGroups as $aGroup) {
+            $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aGroup[$CNF['FIELD_ID']], $sModule);
+            if(!$oGroupProfile)
+                continue;
+
+            $aPrices = $this->_oDb->getPrices(array('type' => 'by_profile_id', 'profile_id' => $oGroupProfile->id()));
+            if(empty($aPrices) || !is_array($aPrices))
+                continue;
+
+            $sTitle = $oGroupProfile->getDisplayName();
+            $sUrl = $oGroupProfile->getUrl();
+
+            foreach($aPrices as $aPrice)
+                $aResult[] = array(
+                    'id' => $aPrice['id'],
+                    'author_id' => $iSellerId,
+                    'name' => $aPrice['name'],
+                    'title' => _t($CNF['T']['txt_cart_item_title'], $sTitle, $aRoles[$aPrice['role_id']], $aPrice['period'], $aPrice['period_unit']),
+                    'description' => '',
+                    'url' => $sUrl,
+                    'price_single' => $aPrice['price'],
+                    'price_recurring' => $aPrice['price'],
+                    'period_recurring' => $aPrice['period'],
+                    'period_unit_recurring' => $aPrice['period_unit']
+               );
+        }
+
+        return $aResult;
+    }
+
+    public function serviceRegisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder);
+    }
+
+    public function serviceRegisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder);
+    }
+
+    public function serviceReregisterCartItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return $this->_serviceReregisterItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder);
+    }
+
+    public function serviceReregisterSubscriptionItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        return $this->_serviceReregisterItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder);
+    }
+
+    public function serviceUnregisterCartItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+        return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder);
+    }
+
+    public function serviceUnregisterSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder, $sLicense)
+    {
+    	return $this->_serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder); 
+    }
+
+    public function serviceCancelSubscriptionItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+    	return true;
+    }
+
+    protected function _serviceRegisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aItem = $this->serviceGetCartItem($iItemId);
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        $aItemInfo = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => $iItemId));
+
+        $mixedPeriod = false;
+        if((int)$aItemInfo['period'] != 0)
+            $mixedPeriod = array(
+                'period' => (int)$aItemInfo['period'], 
+                'period_unit' => $aItemInfo['period_unit'], 
+                'period_reserve' => $CNF['PARAM_RECURRING_RESERVE']
+            );
+
+        if(!$this->setRole($aItemInfo['profile_id'], $iClientId, $aItemInfo['role_id'], $mixedPeriod, $sOrder))
+            return array();
+
+        return $aItem;
+    }
+
+    protected function _serviceReregisterItem($iClientId, $iSellerId, $iItemIdOld, $iItemIdNew, $sOrder)
+    {
+        $aItem = $this->serviceGetCartItem($iItemIdNew);
+        if(empty($aItem) || !is_array($aItem))
+            return array();
+
+        $aItemInfoOld = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => $iItemIdOld));
+        if(empty($aItemInfoOld) || !is_array($aItemInfoOld))
+            return array();
+
+        $aItemInfoNew = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => $iItemIdNew));
+        if(empty($aItemInfoNew) || !is_array($aItemInfoNew))
+            return array();
+
+        if(!$this->unsetRole($aItemInfoOld['profile_id'], $iClientId))
+            return array();
+        
+        $aResult = $this->_serviceRegisterItem($iClientId, $iSellerId, $iItemIdNew, 1, $sOrder);
+        if(empty($aResult) || !is_array($aResult))
+            return array();
+
+    	return $aItem;
+    }
+
+    protected function _serviceUnregisterItem($iClientId, $iSellerId, $iItemId, $iItemCount, $sOrder)
+    {
+        $aItemInfo = $this->_oDb->getPrices(array('type' => 'by_id', 'value' => $iItemId));
+        if(empty($aItemInfo) || !is_array($aItemInfo))
+            return false;
+
+        return $this->unsetRole($aItemInfo['profile_id'], $iClientId);
+    }
+
+
+    /**
      * Data for Notifications module
      */
     public function serviceGetNotificationsData()
@@ -689,6 +971,9 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
             $iPerformerId = bx_get_logged_profile_id();
 
         $oGroupProfile = BxDolProfile::getInstanceByContentAndType($aDataEntry[$CNF['FIELD_ID']], $this->getName());
+        if(!$oGroupProfile)
+            return $sAction == BX_BASE_MOD_GROUPS_ACTION_DELETE ? CHECK_ACTION_RESULT_ALLOWED : _t('_sys_txt_not_found');
+
         $iGroupProfileId = $oGroupProfile->id();
 
         if(!$bRoles && $this->_oDb->isAdmin($iGroupProfileId, $iPerformerId, $aDataEntry))
@@ -897,12 +1182,26 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
     
     public function isFan ($iContentId, $iProfileId = false) 
     {
+        $CNF = &$this->_oConfig->CNF;
+
         $oGroupProfile = BxDolProfile::getInstanceByContentAndType($iContentId, $this->getName());
-        if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS']))
-            return $oGroupProfile && ($oConnection = BxDolConnection::getObjectInstance($this->_oConfig->CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+        if($oGroupProfile && isset($CNF['OBJECT_CONNECTIONS']))
+            return ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+
         return false;
     }
-    
+
+    public function isFanByGroupProfileId ($iGroupProfileId, $iProfileId = false) 
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if($oGroupProfile && isset($CNF['OBJECT_CONNECTIONS']))
+            return ($oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS'])) && $oConnection->isConnected($iProfileId ? $iProfileId : bx_get_logged_profile_id(), $oGroupProfile->id(), true);
+
+        return false;
+    }
+
     public function isInvited ($sKey, $iProfileId) 
     {
         $CNF = &$this->_oConfig->CNF;
@@ -916,17 +1215,35 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         return true;
     }
 
-    public function setRole($iGroupProfileId, $iFanProfileId, $iRole)
+    public function getRole($iGroupProfileId, $iFanProfileId)
     {
-        if(($mixedResult = $this->checkAllowedManageAdmins($iGroupProfileId)) !== CHECK_ACTION_RESULT_ALLOWED)
-            return array('code' => 1, 'msg' => $mixedResult);
+        if(!$this->isFanByGroupProfileId($iGroupProfileId, $iFanProfileId))
+            return false;
+
+        return $this->_oDb->getRole($iGroupProfileId, $iFanProfileId);
+    }
+
+    public function setRole($iGroupProfileId, $iFanProfileId, $iRole, $mixedPeriod = false, $sOrder = '')
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isset($CNF['OBJECT_CONNECTIONS']))
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if(!$oConnection || !$oGroupProfile)
+            return false;
+
+        if(!$oConnection->isConnected($iFanProfileId, $iGroupProfileId, true) && !($oConnection->addConnection($iFanProfileId, $iGroupProfileId) && $oConnection->addConnection($iGroupProfileId, $iFanProfileId)))
+            return false;
 
-        if(!$this->_oDb->setRole($iGroupProfileId, $iFanProfileId, $iRole))
-            return array('code' => 2, 'msg' => _t('_error occured'));
+        if(!$this->_oDb->setRole($iGroupProfileId, $iFanProfileId, $iRole, $mixedPeriod, $sOrder))
+            return false;
 
         $this->onSetRole($iGroupProfileId, $iFanProfileId, $iRole);
 
-        return array('code' => 0);
+        return true;
     }
 
     public function onSetRole($iGroupProfileId, $iFanProfileId, $iRole)
@@ -946,13 +1263,6 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
                 'Role' => $aRoles[$iRole]
             ), BX_EMAIL_NOTIFY);
 
-        // subscribe advanced group members automatically
-        $oConnection = BxDolConnection::getObjectInstance('sys_profiles_subscriptions');
-        if($iRole != 0)
-            $oConnection->addConnection($iFanProfileId, $iGroupProfileId);
-        else 
-            $oConnection->removeConnection($iFanProfileId, $iGroupProfileId);
-
         bx_alert($this->getName(), 'set_role', $aGroupProfileInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
             'object_author_id' => $iGroupProfileId,
             'performer_id' => $iProfileId, 
@@ -968,6 +1278,54 @@ class BxBaseModGroupsModule extends BxBaseModProfileModule
         $this->doAudit($iGroupProfileId, $iFanProfileId, '_sys_audit_action_group_role_changed');
     }
 
+    public function unsetRole($iGroupProfileId, $iFanProfileId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(!isset($CNF['OBJECT_CONNECTIONS']))
+            return false;
+
+        $oConnection = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS']);
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        if(!$oConnection || !$oGroupProfile)
+            return false;
+
+        if($oConnection->isConnected($iFanProfileId, $iGroupProfileId, true) && !$oConnection->removeConnection($iFanProfileId, $iGroupProfileId))
+            return false;
+
+        $iRole = $this->_oDb->getRole($iGroupProfileId, $iFanProfileId);
+
+        if(!$this->_oDb->unsetRole($iGroupProfileId, $iFanProfileId))
+            return false;
+
+        $this->onUnsetRole($iGroupProfileId, $iFanProfileId, $iRole);
+
+        return true;
+    }
+
+    public function onUnsetRole($iGroupProfileId, $iFanProfileId, $iRole)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $iProfileId = bx_get_logged_profile_id();
+        $oGroupProfile = BxDolProfile::getInstance($iGroupProfileId);
+        $aGroupProfileInfo = $this->_oDb->getContentInfoById((int)$oGroupProfile->getContentId());
+
+        bx_alert($this->getName(), 'set_role', $aGroupProfileInfo[$CNF['FIELD_ID']], $iGroupProfileId, array(
+            'object_author_id' => $iGroupProfileId,
+            'performer_id' => $iProfileId, 
+            'fan_id' => $iFanProfileId,
+
+            'content' => $aGroupProfileInfo,
+            'role' => $iRole,
+
+            'group_profile' => $iGroupProfileId, 
+            'profile' => $iProfileId
+        ));
+
+        $this->doAudit($iGroupProfileId, $iFanProfileId, '_sys_audit_action_group_role_changed');
+    }
+
     protected function _getImagesForTimelinePost($aEvent, $aContentInfo, $sUrl, $aBrowseParams = array())
     {
         $CNF = &$this->_oConfig->CNF;

+ 19 - 0
modules/base/groups/template/popup_price.html

@@ -0,0 +1,19 @@
+<div class="bx-base-group-price-form">
+    <div class="bx-base-group-price-form-cnt">
+        __form__
+        <script language="javascript">
+            $(document).ready(function () {
+                $("#__form_id__").ajaxForm({ 
+                    dataType: "json",
+                    beforeSubmit: function (formData, jqForm, options) {
+                        bx_loading($("#__form_id__"), true);
+                    },
+                    success: function (data) {
+                        $(".bx-popup-applied:visible").dolPopupHide();
+                        glGrids.__object__.processJson(data, '__action__');
+                    }
+                });
+            });
+        </script>
+    </div>
+</div>

+ 9 - 0
modules/boonex/classes/classes/BxClssAlertsResponse.php

@@ -16,6 +16,15 @@ class BxClssAlertsResponse extends BxBaseModTextAlertsResponse
         $this->MODULE = 'bx_classes';
         parent::__construct();
     }
+
+    public function response($oAlert)
+    {
+        $CNF = $this->_oModule->_oConfig->CNF;
+
+        if ('bx_classes' == $oAlert->sUnit && 'commentPost' == $oAlert->sAction) {
+            $this->_oModule->_oDb->updateClassStatus($oAlert->iObject, $oAlert->iSender, 'replied');
+        }
+    }
 }
 
 /** @} */

+ 2 - 0
modules/boonex/classes/classes/BxClssConfig.php

@@ -29,6 +29,7 @@ class BxClssConfig extends BxBaseModTextConfig
 
             // database tables
             'TABLE_ENTRIES' => $aModule['db_prefix'] . 'classes',
+            'TABLE_MODULES' => $aModule['db_prefix'] . 'modules',
             'TABLE_ENTRIES_FULLTEXT' => 'title_text',
 
             // database fields
@@ -62,6 +63,7 @@ class BxClssConfig extends BxBaseModTextConfig
             'FIELD_ANONYMOUS' => 'anonymous',
             'FIELD_CMTS_SETTINGS' => 'cmts',
             'FIELD_AVAIL_SETTINGS' => 'avail',
+            'FIELD_MODULE' => 'module_id',
             'FIELDS_WITH_KEYWORDS' => 'auto', // can be 'auto', array of fields or comma separated string of field names, works only when OBJECT_METATAGS is specified
             'FIELDS_DELAYED_PROCESSING' => 'videos', // can be array of fields or comma separated string of field names
 

+ 131 - 0
modules/boonex/classes/classes/BxClssDb.php

@@ -14,10 +14,141 @@
  */
 class BxClssDb extends BxBaseModTextDb
 {
+    protected $_aStatuses = array(
+        1 => 'viewed',
+        2 => 'replied',
+    );
+
     function __construct(&$oConfig)
     {
         parent::__construct($oConfig);
     }
+
+    public function getPrevEntry ($iClassId)
+    {        
+        return $this->_getNextPrevEntry($iClassId, 'DESC');
+    }
+
+    public function getNextEntry ($iClassId)
+    {        
+        return $this->_getNextPrevEntry($iClassId, 'ASC');
+    }
+
+    protected function _getNextPrevEntry ($iClassId, $sSorting = 'DESC')
+    {        
+        $aClass = $this->getRow("SELECT `c`.`id`, `c`.`order`, `m`.`order` as `order_module` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` INNER JOIN `" . $this->_oConfig->CNF['TABLE_MODULES'] . "` AS `m` ON (`m`.`id` = `c`.`module_id`) WHERE `c`.`id` = :class", array('class' => $iClassId));
+        $sQuery = "SELECT `c`.`*`, `m`.`module_title` FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` AS `c` INNER JOIN `" . $this->_oConfig->CNF['TABLE_MODULES'] . "` AS `m` ON (`m`.`id` = `c`.`module_id`) WHERE `m`.`order` <= :order_module AND `c`.`order` <= :order AND `c`.`id` != :id ORDER BY `m`.`order` DESC, `c`.`order` DESC LIMIT 1";
+        
+        $a = $this->getRow($sQuery, array(
+            'order_module' => $aClass['order_module'], 
+            'order' => $aClass['order'],
+            'id' => $aClass['id'],
+        ));
+
+        return $a;
+    }
+
+    public function getEntriesByModule ($iModuleId)
+    {
+        $sQuery = $this->prepare ("SELECT * FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `module_id` = ? ORDER BY `order`", $iModuleId);
+        return $this->getAll($sQuery);
+    }
+
+    public function getEntriesModulesByContext ($iProfileConextId, $bAsPairs = false)
+    {
+        $sQuery = $this->prepare ("SELECT * FROM `" . $this->_oConfig->CNF['TABLE_MODULES'] . "` WHERE `profile_id` = ? ORDER BY `order`", $iProfileConextId);
+        if ($bAsPairs)
+            return $this->getPairs($sQuery, 'id', 'module_title');
+        else
+            return $this->getAll($sQuery);
+    }
+
+    public function updateModulesOrder ($iProfileConextId, $aModulesOrder)
+    {
+        $iAffected = 0;
+        foreach ($aModulesOrder as $iOrder => $iModuleId) {
+            $iAffected += ($this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_MODULES'] . "` SET `order` = :order WHERE `profile_id` = :profile_id AND `id` = :module_id", array(
+                'order' => $iOrder,
+                'profile_id' => $iProfileConextId,
+                'module_id' => $iModuleId,
+            )) ? 1 : 0);
+        }        
+        return $iAffected;
+    }
+
+    public function getModuleMaxOrder ($iProfileConextId)
+    {
+        return $this->getOne("SELECT `order` + 1 FROM `" . $this->_oConfig->CNF['TABLE_MODULES'] . "` WHERE `profile_id` = :profile_context ORDER BY `order` DESC LIMIT 1", array(
+            'profile_context' => $iProfileConextId,
+        ));
+    }
+
+    public function getClassMaxOrder ($iProfileConextId, $iModuleId)
+    {
+        return $this->getOne("SELECT `order` + 1 FROM `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` WHERE `allow_view_to` = :profile_context AND `module_id` = :module ORDER BY `order` DESC LIMIT 1", array(
+            'profile_context' => -$iProfileConextId,
+            'module' => $iModuleId,
+        ));
+    }
+
+    public function updateClassesOrder($iProfileConextId, $iModuleId, $aClassesOrder)
+    {
+        $iAffected = 0;
+        foreach ($aClassesOrder as $iOrder => $iClassId) {
+            $iAffected += ($this->query("UPDATE `" . $this->_oConfig->CNF['TABLE_ENTRIES'] . "` SET `order` = :order, `module_id` = :module_id WHERE `allow_view_to` = :profile_context AND `id` = :class_id", array(
+                'order' => $iOrder,
+                'profile_context' => -$iProfileConextId,
+                'module_id' => $iModuleId,
+                'class_id' => $iClassId,
+            )) ? 1 : 0);
+        }        
+        return $iAffected;
+    }
+
+    public function isClassCompleted($iClassId, $iStudentProfileId)
+    {
+        if (!($aContentInfo = $this->getContentInfoById($iClassId)))
+            return false;
+
+        return $this->getClassStatus($iClassId, $iStudentProfileId, (int)$aContentInfo['completed_when']);
+    }
+
+    public function getClassStatus($iClassId, $iStudentProfileId, $mixedStatus)
+    {
+        if (is_numeric($mixedStatus) && is_int($mixedStatus) && isset($this->_aStatuses[$mixedStatus]))
+            $mixedStatus = $this->_aStatuses[$mixedStatus];
+        elseif (!in_array($mixedStatus, $this->_aStatuses, true))
+            return false;
+
+        return $this->getOne("SELECT `$mixedStatus` FROM `bx_classes_statuses` WHERE `class_id` = :class AND `student_profile_id` = :student", array(
+            'class' => $iClassId,
+            'student' => $iStudentProfileId,
+        ));
+    }
+
+    public function updateClassStatus($iClassId, $iStudentProfileId, $sStatus)
+    {
+        if (!in_array($sStatus, $this->_aStatuses))
+            return false;
+
+        if ($this->getOne("SELECT `id` FROM `bx_classes_statuses` WHERE `class_id` = :class AND `student_profile_id` = :student", array(
+            'class' => $iClassId,
+            'student' => $iStudentProfileId,
+        ))) {
+            return $this->query("UPDATE `bx_classes_statuses` SET `$sStatus` = :ts WHERE `class_id` = :class AND `student_profile_id` = :student", array(
+                'ts' => time(),
+                'class' => $iClassId,
+                'student' => $iStudentProfileId,
+            ));
+        }
+        else {
+            return $this->query("INSERT INTO `bx_classes_statuses` SET `class_id` = :class, `student_profile_id` = :student, `$sStatus` = :ts", array(
+                'ts' => time(),
+                'class' => $iClassId,
+                'student' => $iStudentProfileId,
+            ));
+        }
+    }
 }
 
 /** @} */

+ 38 - 0
modules/boonex/classes/classes/BxClssFormEntry.php

@@ -15,6 +15,7 @@
 class BxClssFormEntry extends BxBaseModTextFormEntry
 {
     protected $_sGhostTemplateCover = 'form_ghost_template_cover.html';
+    protected $_oProfileContext = null;
 	
     public function __construct($aInfo, $oTemplate = false)
     {
@@ -98,6 +99,31 @@ class BxClssFormEntry extends BxBaseModTextFormEntry
         }
 
         parent::initChecker ($aValues, $aSpecificValues);
+
+        if (isset($this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']])) {
+
+            // try to get context from form POST
+            $iProfileId = bx_get($CNF['FIELD_ALLOW_VIEW_TO']);
+
+            // try to get context from the form saved value (in case of "edit" form)
+            if ($iProfileId >= 0 && $this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]['value'] < 0)
+                $iProfileId = $this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]['value'];
+
+            // try to get context from GET param (in case of "create" form)
+            if ($iProfileId >= 0)
+                $iProfileId = bx_get('profile_id');
+
+            $iProfileId = abs($iProfileId);
+            $this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]['value'] = -$iProfileId;
+            $this->aInputs[$CNF['FIELD_ALLOW_VIEW_TO']]['type'] = 'hidden';
+
+            if ($iProfileId)
+                $this->_oProfileContext = BxDolProfile::getInstance($iProfileId);
+
+            if (isset($this->aInputs[$CNF['FIELD_MODULE']]) && $this->_oProfileContext) {
+                $this->aInputs[$CNF['FIELD_MODULE']]['values'] = $this->_oModule->_oDb->getEntriesModulesByContext($this->_oProfileContext->id(), true);
+            }
+        }
     }
 
     public function insert ($aValsToAdd = array(), $isIgnore = false)
@@ -128,6 +154,8 @@ class BxClssFormEntry extends BxBaseModTextFormEntry
 
         $aValsToAdd[$CNF['FIELD_STATUS']] = $aValsToAdd[$CNF['FIELD_PUBLISHED']] > $aValsToAdd[$CNF['FIELD_ADDED']] ? 'awaiting' : 'active';
 
+        $aValsToAdd['order'] = $this->_oModule->_oDb->getClassMaxOrder(abs($this->getCleanValue($CNF['FIELD_ALLOW_VIEW_TO'])), $this->getCleanValue($CNF['FIELD_MODULE']));
+
         $iContentId = parent::insert ($aValsToAdd, $isIgnore);
         if(!empty($iContentId)){
             $this->processFiles($CNF['FIELD_COVER'], $iContentId, true);
@@ -175,6 +203,16 @@ class BxClssFormEntry extends BxBaseModTextFormEntry
             'editor_id' => isset($CNF['FIELD_TEXT_ID']) ? $CNF['FIELD_TEXT_ID'] : ''
     	);
     }
+
+    function getCode($bDynamicMode = false)
+    {
+        if (!$this->_bViewMode && !$this->_oProfileContext)
+            return MsgBox(_t('_bx_classes_txt_err_cant_add_class_without_context'));
+
+        // TODO: check permission for adding to context
+
+        return parent::getCode($bDynamicMode);
+    }
 }
 
 /** @} */

+ 322 - 0
modules/boonex/classes/classes/BxClssModule.php

@@ -9,6 +9,12 @@
  * @{
  */
 
+
+define('BX_CLASSES_AVAIL_ALWAYS', 1);
+define('BX_CLASSES_AVAIL_PREV_CLASS_COMPLETED', 2);
+define('BX_CLASSES_AVAIL_AFTER_START_DATE', 3);
+define('BX_CLASSES_AVAIL_AFTER_START_DATE_PREV_CLASS_COMPLETED', 4);
+
 /**
  * Classes module
  */
@@ -28,6 +34,137 @@ class BxClssModule extends BxBaseModTextModule
         ));
     }
 
+    public function actionReorderClasses($iProfileConextId = 0)
+    {
+        $oProfileContext = $iProfileConextId ? BxDolProfile::getInstance($iProfileConextId) : null;
+        if (!$oProfileContext) {
+            echo _t('_sys_txt_error_occured');
+            exit;
+        }
+
+        if (!$this->serviceIsCourseAdmin($oProfileContext->id())) {
+            echo _t('_sys_txt_access_denied');
+            exit;
+        }
+
+        foreach ($_REQUEST as $k => $v) {
+            if (0 !== strncmp($k, 'classes_order_', 14))
+                continue;
+            $iModuleId = (int)str_replace('classes_order_', '', $k);
+            if (!$iModuleId)
+                continue;
+            
+            $aClassesOrder = explode(',', $v);
+            if (!$aClassesOrder || !is_array($aClassesOrder))
+                continue;
+
+            $this->_oDb->updateClassesOrder($iProfileConextId, $iModuleId, $aClassesOrder);
+        }
+    }
+
+    public function actionReorderModules($iProfileConextId = 0)
+    {
+        $oProfileContext = $iProfileConextId ? BxDolProfile::getInstance($iProfileConextId) : null;
+        if (!$oProfileContext) {
+            echo _t('_sys_txt_error_occured');
+            exit;
+        }
+
+        if (!$this->serviceIsCourseAdmin($oProfileContext->id())) {
+            echo _t('_sys_txt_access_denied');
+            exit;
+        }
+
+        $aModulesOrder = bx_get('modules_order');
+        if (!$aModulesOrder || !is_array($aModulesOrder)) {
+            echo _t('_sys_txt_error_occured');
+            exit;
+        }
+
+        $this->_oDb->updateModulesOrder($iProfileConextId, $aModulesOrder);
+    }
+
+    public function actionAddModule($iProfileConextId = 0)
+    {
+        $oProfileContext = $iProfileConextId ? BxDolProfile::getInstance($iProfileConextId) : null;
+        if (!$oProfileContext) {
+            echoJson(array('action' => 'ShowMsg', 'msg' => _t('_sys_txt_error_occured')));
+            exit;
+        }
+
+        if (!$this->serviceIsCourseAdmin($oProfileContext->id())) {
+            echoJson(array('action' => 'ShowMsg', 'msg' => _t('_sys_txt_access_denied')));
+            exit;
+        }
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'bx-classes-module-add',
+                'action' => BX_DOL_URL_ROOT . 'modules/index.php?r=classes/add_module/' . $iProfileConextId,
+                'method' => 'post',
+            ),
+            'params' => array (
+                'db' => array(
+                    'submit_name' => 'do_submit',
+                    'table' => $this->_oConfig->CNF['TABLE_MODULES'],
+                    'key' => 'id',
+                ),
+            ),
+            'inputs' => array(
+                'module_title' => array(
+                    'type' => 'text',
+                    'name' => 'module_title',
+                    'caption' => _t('_bx_classes_form_entry_input_module_title'),
+                    'checker' => array(
+                        'func' => 'Avail',
+                        'error' => _t('_bx_classes_form_entry_input_module_title_err'),
+                    ),
+                    'db' => array('pass' => 'Xss'),
+                ),
+                'submit' => array(
+                    'type' => 'input_set',
+                    0 => array (
+                        'type' => 'submit',
+                        'name' => 'do_submit',
+                        'value' => _t('_Submit'),
+                    ),
+                    1 => array (
+                        'type' => 'reset',
+                        'name' => 'close',
+                        'value' => _t('_Cancel'),
+                        'attrs' => array(
+                            'class' => 'bx-def-margin-sec-left',
+                            'onclick' => "$('.bx-popup-applied:visible').dolPopupHide();",
+                        ),
+                    ),
+                ),
+            ),
+        );
+        $oForm = new BxTemplFormView($aForm);
+        $oForm->initChecker();
+        if ($oForm->isSubmittedAndValid()) {
+            $iModuleId = $oForm->insert(array(
+                'profile_id' => $iProfileConextId,
+                'author' => bx_get_logged_profile_id(),
+                'added' => time(),
+                'changed' => time(),
+                'order' => $this->_oDb->getModuleMaxOrder ($iProfileConextId),
+            ));
+            if ($iModuleId) {
+                echoJson(array('action' => 'ReloadLessonsAndClosePopup'));
+            }
+            else {
+                echoJson(array('action' => 'ShowMsg', 'msg' => _t('_sys_txt_error_occured')));
+            }
+        } 
+        else {
+            echo $this->_oTemplate->parseHtmlByName('classes_add_module_form.html', array(
+                'form' => $oForm->getCode(),
+                'profile_id' => $iProfileConextId,
+            ));
+        }
+    }
+
     /**
      * Entry post for Timeline module
      */
@@ -65,6 +202,191 @@ class BxClssModule extends BxBaseModTextModule
 
         return parent::serviceCheckAllowedCommentsView($iContentId, $sObjectComments);
     }
+
+    public function serviceClassesInContext ($iContextProfileId = 0)
+    {
+        if (!$iContextProfileId)
+            $iContextProfileId = (int)bx_get('id') ? bx_get('id') : bx_get('profile_id');
+
+        if (!($oContextProfile = BxDolProfile::getInstance($iContextProfileId)))
+            return MsgBox(_t('_sys_txt_error_occured'));
+
+        $mixedViewAllowed = $oContextProfile->checkAllowedProfileView();
+        if (CHECK_ACTION_RESULT_ALLOWED !== $mixedViewAllowed)
+            return MsgBox($mixedViewAllowed);
+
+        $aInputs = array();
+        $aModules = $this->_oDb->getEntriesModulesByContext($iContextProfileId);
+        foreach ($aModules as $aModule) {
+            $iCounterCompleted = 0;
+            $iCounterAvail = 0;
+            $iCounterNa = 0;
+
+            $aInputs['module' . $aModule['id']] = array(
+                'type' => 'block_header',
+                'caption' => '&nbsp;' . bx_process_output($aModule['module_title']),
+                'collapsed' => false,
+                'attrs' => array('id' => 'module_' . $aModule['id'], 'class' => ''),
+            );
+            $aClasses = $this->_oDb->getEntriesByModule($aModule['id']);
+            $sContent = '';
+            if ($aClasses) {
+                foreach ($aClasses as $aClass) {
+                    $sStatusClass = '';
+                    // check class availability
+                    $mixedAvailability = $this->checkAllowedViewForProfile($aClass, bx_get_logged_profile_id());
+                    $sTip = '';
+                    if ($mixedAvailability !== CHECK_ACTION_RESULT_ALLOWED) {
+                        ++$iCounterNa;
+                        $sStatusClass = 'bx-classes-class-status-na';
+                        $sTip = strip_tags($mixedAvailability);
+                    }
+                    // check if class is completed
+                    elseif ($this->serviceIsClassCompleted($aClass['id'])) {
+                        ++$iCounterCompleted;
+                        $sStatusClass = 'bx-classes-class-status-completed';
+                    }
+                    // class is available
+                    else {
+                        ++$iCounterAvail;
+                        $sStatusClass = 'bx-classes-class-status-avail';
+                    }
+   
+                    $aContent = array (
+                        'title' => bx_process_output($aClass['title']),
+                        'url' => $this->serviceGetLink($aClass['id']),
+                        'tip' => bx_html_attribute($sTip),
+                        'date_created' => bx_time_js($aClass['added']),
+                        'start_date' => $aClass['start_date'] ? _t('_bx_classes_txt_start_x', bx_time_js($aClass['start_date'], BX_FORMAT_DATE_TIME)) : '',
+                        'end_date' => $aClass['end_date'] ? _t('_bx_classes_txt_due_x', bx_time_js($aClass['end_date'], BX_FORMAT_DATE_TIME)) : '',
+                    );
+
+                    $sContent .= $this->_oTemplate->parseHtmlByName('classes_class_row.html', array(
+                        'id' => $aClass['id'],                        
+                        'status' => $sStatusClass,
+                        'bx_if:completed' => array(
+                            'condition' => 'bx-classes-class-status-completed' == $sStatusClass,
+                            'content' => $aContent,
+                        ),
+                        'bx_if:avail' => array(
+                            'condition' => 'bx-classes-class-status-avail' == $sStatusClass,
+                            'content' => $aContent,
+                        ),
+                        'bx_if:na' => array(
+                            'condition' => 'bx-classes-class-status-na' == $sStatusClass,
+                            'content' => $aContent,
+                        ),
+                    ));
+                }
+            }
+
+            if (0 == $iCounterAvail) {
+                $aInputs['module' . $aModule['id']]['collapsed'] = true;
+            }
+
+            $aInputs['class_module' . $aModule['id']] = array(
+                'type' => 'custom',
+                'name' => 'class_module' . $aModule['id'],
+                'caption' => '',
+                'content' => $sContent,
+            );
+
+        }
+
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'bx-classes-list-view',
+            ),
+            'inputs' => $aInputs,
+        );
+        $oForm = new BxTemplFormView($aForm);
+        $oForm->setShowEmptySections(true);
+
+        $this->_oTemplate->addCss('main.css');
+        $this->_oTemplate->addJs('main.js');
+
+        return $this->_oTemplate->parseHtmlByName('classes_in_context.html', array(
+            'form' => $oForm->getCode(),
+            'bx_if:admin' => array(
+                'condition' => isAdmin() || $this->serviceIsCourseAdmin($oContextProfile->id()),
+                'content' => array(
+                    'context_profile_id' => $iContextProfileId,
+                    'new_class_url' => BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=create-class&profile_id=' . $oContextProfile->id()),
+                ),
+            ),
+        ));
+    }
+
+    public function serviceIsClassCompleted ($iClassId, $iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        return $this->_oDb->isClassCompleted($iClassId, $iProfileId);
+    }
+
+    public function serviceIsCourseAdmin ($iContextProfileId, $iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        if (!($oProfileContext = BxDolProfile::getInstance($iContextProfileId)))
+            return false;
+        
+        return bx_srv($oProfileContext->getModule(), 'is_admin', array($oProfileContext->id(), $iProfileId));
+    }
+
+    public function serviceIsClassAdmin ($aDataEntry, $iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        if (!($oProfileContext = BxDolProfile::getInstance(abs($aDataEntry['allow_view_to']))))
+            return false;
+        
+        return bx_srv($oProfileContext->getModule(), 'is_admin', array($oProfileContext->id(), $iProfileId));
+    }
+
+    public function serviceIsPrevClassCompleted ($aDataEntry, $iProfileId = 0)
+    {
+        if (!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        if (!($aPrevClass = $this->_oDb->getPrevEntry ($aDataEntry['id'])))
+            return true; // if there is no prev class it's considered as completed
+        
+        return $this->serviceIsClassCompleted($aPrevClass['id'], $iProfileId);
+    }
+
+    public function serviceCheckAvailabilityForProfile ($aDataEntry, $iProfileId)
+    {
+        if (BX_CLASSES_AVAIL_ALWAYS == $aDataEntry['avail'] || isAdmin() || $this->serviceIsClassAdmin ($aDataEntry, $iProfileId))
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        // check start date
+        if ((BX_CLASSES_AVAIL_AFTER_START_DATE == $aDataEntry['avail'] || BX_CLASSES_AVAIL_AFTER_START_DATE_PREV_CLASS_COMPLETED == $aDataEntry['avail']) && $aDataEntry['start_date'] > time()) {
+            return _t('_bx_classes_txt_err_not_avail_before_start_date', bx_time_js($aDataEntry['start_date'], BX_FORMAT_DATE_TIME, true));
+        }
+
+        // check availability
+        if ((BX_CLASSES_AVAIL_PREV_CLASS_COMPLETED == $aDataEntry['avail'] || BX_CLASSES_AVAIL_AFTER_START_DATE_PREV_CLASS_COMPLETED == $aDataEntry['avail']) && !$this->serviceIsPrevClassCompleted($aDataEntry, $iProfileId)) {
+            if (!($aPrevClass = $this->_oDb->getPrevEntry ($aDataEntry['id'])))
+                return _t('_sys_txt_error_occured');
+            return _t('_bx_classes_txt_err_not_avail_before_prev_class_completed', $this->serviceGetLink($aPrevClass['id']), bx_process_output($aPrevClass['title']));
+        }
+        
+
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    protected function _serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId)
+    {
+        $mixed = parent::_serviceCheckAllowedViewForProfile ($aDataEntry, $isPerformAction, $iProfileId);
+        if (CHECK_ACTION_RESULT_ALLOWED === $mixed)
+            return $this->serviceCheckAvailabilityForProfile ($aDataEntry, $iProfileId);
+        else
+            return $mixed;
+    }
 }
 
 /** @} */

+ 12 - 0
modules/boonex/classes/classes/BxClssPageEntry.php

@@ -27,6 +27,18 @@ class BxClssPageEntry extends BxBaseModTextPageEntry
     		'icon' => ''
     	)));
     }
+
+    public function getCode ()
+    {
+        $s = parent::getCode ();
+
+        // mark class as viewed
+        $CNF = &$this->_oModule->_oConfig->CNF;
+        if ($this->_aContentInfo && isLogged())
+            $this->_oModule->_oDb->updateClassStatus($this->_aContentInfo[$CNF['FIELD_ID']], bx_get_logged_profile_id(), 'viewed');
+
+        return $s;
+    }
 }
 
 /** @} */

+ 14 - 1
modules/boonex/classes/install/langs/en.xml

@@ -57,10 +57,14 @@
     <string name="_bx_classes_form_entry_input_sys_date_changed"><![CDATA[Updated]]></string>
     <string name="_bx_classes_form_entry_input_date_changed"><![CDATA[Updated]]></string>
     <string name="_bx_classes_form_entry_input_date_start_date"><![CDATA[Start date]]></string>
-    <string name="_bx_classes_form_entry_input_date_end_date"><![CDATA[End date]]></string>
+    <string name="_bx_classes_form_entry_input_date_end_date"><![CDATA[Due date]]></string>
     <string name="_bx_classes_form_entry_input_sys_attachments"><![CDATA[Attachments]]></string>
     <string name="_bx_classes_form_entry_input_cmts"><![CDATA[Comments settings]]></string>
     <string name="_bx_classes_form_entry_input_avail"><![CDATA[Availability]]></string>
+    <string name="_bx_classes_form_entry_input_module"><![CDATA[Module]]></string>
+    <string name="_bx_classes_form_entry_input_completed_when"><![CDATA[Class is completed when]]></string>
+    <string name="_bx_classes_form_entry_input_module_title"><![CDATA[Module title]]></string>
+    <string name="_bx_classes_form_entry_input_module_title_err"><![CDATA[Module title is mandatory]]></string>
 
     <string name="_bx_classes_form_poll"><![CDATA[Polls]]></string>
     <string name="_bx_classes_form_poll_display_add"><![CDATA[Add Poll]]></string>
@@ -211,6 +215,10 @@
     <string name="_bx_classes_cmts_all_shown"><![CDATA[All comments are shown]]></string>
     <string name="_bx_classes_cmts_own_and_admins"><![CDATA[Own and admin comments are shown]]></string>
 
+    <string name="_bx_classes_pre_lists_completed_when"><![CDATA[Class is completed when]]></string>
+    <string name="_bx_classes_completed_when_viewed"><![CDATA[Viewed]]></string>
+    <string name="_bx_classes_completed_when_replied"><![CDATA[Replied]]></string>
+
     <string name="_bx_classes_txt_all_entries_in"><![CDATA[<a href="{0}">All Classes in {1} ({2})</a>]]></string>
     <string name="_bx_classes_txt_all_entries_by"><![CDATA[{0} classes]]></string>
     <string name="_bx_classes_txt_sample_single"><![CDATA[class]]></string>
@@ -223,8 +231,13 @@
     <string name="_bx_classes_txt_processing_awaiting"><![CDATA[Scheduled for publishing as soon as video conversion is complete.]]></string>
     <string name="_bx_classes_txt_processing_failed"><![CDATA[Class isn't published because video conversion failed, edit video to fix the issue.]]></string>
     <string name="_bx_classes_txt_scheduled_awaiting"><![CDATA[Class publishing date is {date_publish_uf}]]></string>
+    <string name="_bx_classes_txt_start_x"><![CDATA[start {0}]]></string>
+    <string name="_bx_classes_txt_due_x"><![CDATA[due {0}]]></string>
 
     <string name="_bx_classes_txt_err_cannot_perform_action"><![CDATA[Cannot perform action. Please report.]]></string>
+    <string name="_bx_classes_txt_err_cant_add_class_without_context"><![CDATA[Class can be added into context only]]></string>
+    <string name="_bx_classes_txt_err_not_avail_before_start_date"><![CDATA[Class will be available {0}]]></string>
+    <string name="_bx_classes_txt_err_not_avail_before_prev_class_completed"><![CDATA[Class will be available after completing previous class - <a href="{0}">{1}</a>]]></string>
 
     <string name="_bx_classes_txt_poll_view_answers"><![CDATA[poll]]></string>
     <string name="_bx_classes_txt_poll_view_results"><![CDATA[results]]></string>

+ 6 - 0
modules/boonex/classes/install/sql/enable.sql

@@ -98,6 +98,10 @@ INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `lay
 INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
 ('bx_classes_administration', 1, 'bx_classes', '_bx_classes_page_block_title_system_manage_administration', '_bx_classes_page_block_title_manage', 11, 192, 'service', 'a:3:{s:6:\"module\";s:10:\"bx_classes\";s:6:\"method\";s:12:\"manage_tools\";s:6:\"params\";a:1:{i:0;s:14:\"administration\";}}', 0, 1, 0);
 
+-- PAGES: add page block to profiles modules (trigger* page objects are processed separately upon modules enable/disable)
+SET @iPBCellGroup = 4;
+INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
+('trigger_page_group_view_entry', @iPBCellGroup, 'bx_classes', '_bx_classes_page_block_title_group_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:10:"bx_classes";s:6:"method";s:18:"classes_in_context";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 0);
 
 -- MENU: add to "add content" menu
 
@@ -361,6 +365,8 @@ INSERT INTO `sys_alerts` (`unit`, `action`, `handler_id`) VALUES
 ('system', 'save_setting', @iHandler),
 ('profile', 'delete', @iHandler),
 
+('bx_classes', 'commentPost', @iHandler),
+
 ('bx_classes_videos_mp4', 'transcoded', @iHandler);
 
 

+ 76 - 36
modules/boonex/classes/install/sql/install.sql

@@ -3,14 +3,17 @@ CREATE TABLE IF NOT EXISTS `bx_classes_classes` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `author` int(11) NOT NULL,
   `added` int(11) NOT NULL,
-  `changed` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,  
   `published` int(11) NOT NULL,
+  `module_id` int(10) unsigned NOT NULL,
+  `order` int(11) NOT NULL,
   `start_date` int(11) NOT NULL,
   `end_date` int(11) NOT NULL,
   `thumb` int(11) NOT NULL,
   `title` varchar(255) NOT NULL,
   `avail` int(11) NOT NULL,
   `cmts` int(11) NOT NULL,
+  `completed_when` int(11) NOT NULL,
   `text` mediumtext NOT NULL,
   `labels` text NOT NULL,
   `location` text NOT NULL,
@@ -33,6 +36,29 @@ CREATE TABLE IF NOT EXISTS `bx_classes_classes` (
   FULLTEXT KEY `title_text` (`title`,`text`)
 );
 
+-- TABLE: modules
+CREATE TABLE IF NOT EXISTS `bx_classes_modules` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `module_title` varchar(255) NOT NULL,
+  `author` int(11) NOT NULL,
+  `added` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,
+  `order` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+);
+
+-- TABLE: status
+CREATE TABLE IF NOT EXISTS `bx_classes_statuses` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `class_id` int(10) unsigned NOT NULL,
+  `student_profile_id` int(11) NOT NULL,
+  `viewed` int(11) NOT NULL,
+  `replied` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE `class_student` (`class_id`,`student_profile_id`)
+);
+
 -- TABLE: storages & transcoders
 CREATE TABLE IF NOT EXISTS `bx_classes_covers` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
@@ -404,10 +430,12 @@ INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `ch
 ('bx_classes', 'bx_classes', 'title', '', '', 0, 'text', '_bx_classes_form_entry_input_sys_title', '_bx_classes_form_entry_input_title', '', 1, 0, 0, '', '', '', 'Avail', '', '_bx_classes_form_entry_input_title_err', 'Xss', '', 1, 0),
 ('bx_classes', 'bx_classes', 'avail', '', '#!bx_classes_avail', 0, 'select', '', '_bx_classes_form_entry_input_avail', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
 ('bx_classes', 'bx_classes', 'cmts', '', '#!bx_classes_cmts', 0, 'select', '', '_bx_classes_form_entry_input_cmts', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_classes', 'bx_classes', 'completed_when', '', '#!bx_classes_completed_when', 0, 'select', '', '_bx_classes_form_entry_input_completed_when', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_classes', 'bx_classes', 'module_id', '', '', 0, 'select', '', '_bx_classes_form_entry_input_module', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
 ('bx_classes', 'bx_classes', 'added', '', '', 0, 'datetime', '_bx_classes_form_entry_input_sys_date_added', '_bx_classes_form_entry_input_date_added', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_classes', 'bx_classes', 'changed', '', '', 0, 'datetime', '_bx_classes_form_entry_input_sys_date_changed', '_bx_classes_form_entry_input_date_changed', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
-('bx_classes', 'bx_classes', 'start_date', '', '', 0, 'datetime', '', '_bx_classes_form_entry_input_date_start_date', '', 0, 0, 0, '', '', '', '', '', '', 'DateTimeTs', '', 1, 0),
-('bx_classes', 'bx_classes', 'end_date', '', '', 0, 'datetime', '', '_bx_classes_form_entry_input_date_end_date', '', 0, 0, 0, '', '', '', '', '', '', 'DateTimeTs', '', 1, 0),
+('bx_classes', 'bx_classes', 'start_date', '', '', 0, 'datetime', '', '_bx_classes_form_entry_input_date_start_date', '', 0, 0, 0, '', '', '', '', '', '', 'DateTimeUtc', '', 1, 0),
+('bx_classes', 'bx_classes', 'end_date', '', '', 0, 'datetime', '', '_bx_classes_form_entry_input_date_end_date', '', 0, 0, 0, '', '', '', '', '', '', 'DateTimeUtc', '', 1, 0),
 ('bx_classes', 'bx_classes', 'attachments', '', '', 0, 'custom', '_bx_classes_form_entry_input_sys_attachments', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_classes', 'bx_classes', 'labels', '', '', 0, 'custom', '_sys_form_input_sys_labels', '_sys_form_input_labels', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
 ('bx_classes', 'bx_classes', 'anonymous', '', '', 0, 'switcher', '_sys_form_input_sys_anonymous', '_sys_form_input_anonymous', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0);
@@ -416,42 +444,47 @@ INSERT INTO `sys_form_inputs`(`object`, `module`, `name`, `value`, `values`, `ch
 
 INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES 
 ('bx_classes_entry_add', 'covers', 2147483647, 1, 1),
-('bx_classes_entry_add', 'title', 2147483647, 1, 2),
-('bx_classes_entry_add', 'text', 2147483647, 1, 3),
-('bx_classes_entry_add', 'attachments', 2147483647, 1, 4),
-('bx_classes_entry_add', 'pictures', 2147483647, 1, 5),
-('bx_classes_entry_add', 'videos', 2147483647, 1, 6),
-('bx_classes_entry_add', 'files', 2147483647, 1, 7),
-('bx_classes_entry_add', 'polls', 2147483647, 1, 8),
-('bx_classes_entry_add', 'start_date', 2147483647, 1, 9),
-('bx_classes_entry_add', 'end_date', 2147483647, 1, 10),
-('bx_classes_entry_add', 'avail', 2147483647, 1, 11),
-('bx_classes_entry_add', 'cmts', 2147483647, 1, 12),
-('bx_classes_entry_add', 'allow_view_to', 2147483647, 1, 13),
-('bx_classes_entry_add', 'do_publish', 2147483647, 1, 14),
+('bx_classes_entry_add', 'module_id', 2147483647, 1, 2),
+('bx_classes_entry_add', 'title', 2147483647, 1, 3),
+('bx_classes_entry_add', 'text', 2147483647, 1, 4),
+('bx_classes_entry_add', 'attachments', 2147483647, 1, 5),
+('bx_classes_entry_add', 'pictures', 2147483647, 1, 6),
+('bx_classes_entry_add', 'videos', 2147483647, 1, 7),
+('bx_classes_entry_add', 'files', 2147483647, 1, 8),
+('bx_classes_entry_add', 'polls', 2147483647, 1, 9),
+('bx_classes_entry_add', 'start_date', 2147483647, 1, 10),
+('bx_classes_entry_add', 'end_date', 2147483647, 1, 11),
+('bx_classes_entry_add', 'completed_when', 2147483647, 1, 12),
+('bx_classes_entry_add', 'avail', 2147483647, 1, 13),
+('bx_classes_entry_add', 'cmts', 2147483647, 1, 14),
+('bx_classes_entry_add', 'allow_view_to', 2147483647, 1, 15),
+('bx_classes_entry_add', 'do_publish', 2147483647, 1, 16),
 
 ('bx_classes_entry_delete', 'delete_confirm', 2147483647, 1, 1),
 ('bx_classes_entry_delete', 'do_submit', 2147483647, 1, 2),
 
 ('bx_classes_entry_edit', 'covers', 2147483647, 1, 1),
-('bx_classes_entry_edit', 'title', 2147483647, 1, 2),
-('bx_classes_entry_edit', 'text', 2147483647, 1, 3),
-('bx_classes_entry_edit', 'attachments', 2147483647, 1, 4),
-('bx_classes_entry_edit', 'pictures', 2147483647, 1, 5),
-('bx_classes_entry_edit', 'videos', 2147483647, 1, 6),
-('bx_classes_entry_edit', 'files', 2147483647, 1, 7),
-('bx_classes_entry_edit', 'polls', 2147483647, 1, 8),
-('bx_classes_entry_edit', 'start_date', 2147483647, 1, 9),
-('bx_classes_entry_edit', 'end_date', 2147483647, 1, 10),
-('bx_classes_entry_edit', 'avail', 2147483647, 1, 11),
-('bx_classes_entry_edit', 'cmts', 2147483647, 1, 12),
-('bx_classes_entry_edit', 'allow_view_to', 2147483647, 1, 13),
-('bx_classes_entry_edit', 'do_submit', 2147483647, 1, 14),
-
-('bx_classes_entry_view', 'added', 2147483647, 1, 1),
-('bx_classes_entry_view', 'changed', 2147483647, 1, 2),
-('bx_classes_entry_view', 'start_date', 2147483647, 1, 3),
-('bx_classes_entry_view', 'end_date', 2147483647, 1, 4);
+('bx_classes_entry_edit', 'module_id', 2147483647, 1, 2),
+('bx_classes_entry_edit', 'title', 2147483647, 1, 3),
+('bx_classes_entry_edit', 'text', 2147483647, 1, 4),
+('bx_classes_entry_edit', 'attachments', 2147483647, 1, 5),
+('bx_classes_entry_edit', 'pictures', 2147483647, 1, 6),
+('bx_classes_entry_edit', 'videos', 2147483647, 1, 7),
+('bx_classes_entry_edit', 'files', 2147483647, 1, 8),
+('bx_classes_entry_edit', 'polls', 2147483647, 1, 9),
+('bx_classes_entry_edit', 'start_date', 2147483647, 1, 10),
+('bx_classes_entry_edit', 'end_date', 2147483647, 1, 11),
+('bx_classes_entry_edit', 'completed_when', 2147483647, 1, 12),
+('bx_classes_entry_edit', 'avail', 2147483647, 1, 13),
+('bx_classes_entry_edit', 'cmts', 2147483647, 1, 14),
+('bx_classes_entry_edit', 'allow_view_to', 2147483647, 1, 15),
+('bx_classes_entry_edit', 'do_submit', 2147483647, 1, 16),
+
+('bx_classes_entry_view', 'module_id', 2147483647, 1, 1),
+('bx_classes_entry_view', 'added', 2147483647, 1, 2),
+('bx_classes_entry_view', 'changed', 2147483647, 1, 3),
+('bx_classes_entry_view', 'start_date', 2147483647, 1, 4),
+('bx_classes_entry_view', 'end_date', 2147483647, 1, 5);
 
 -- FORMS: poll
 INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
@@ -490,8 +523,15 @@ INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUE
 
 INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
 ('bx_classes_cmts', '1', 1, '_bx_classes_cmts_disabled', ''),
-('bx_classes_cmts', '2', 2, '_bx_classes_cmts_all_shown', ''),
-('bx_classes_cmts', '3', 3, '_bx_classes_cmts_own_and_admins', '');
+('bx_classes_cmts', '2', 2, '_bx_classes_cmts_all_shown', '');
+
+
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_classes_completed_when', '_bx_classes_pre_lists_completed_when', 'bx_classes', '0');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_classes_completed_when', '1', 1, '_bx_classes_completed_when_viewed', ''),
+('bx_classes_completed_when', '2', 2, '_bx_classes_completed_when_replied', '');
 
 -- COMMENTS
 INSERT INTO `sys_objects_cmts` (`Name`, `Module`, `Table`, `CharsPostMin`, `CharsPostMax`, `CharsDisplayMax`, `Html`, `PerView`, `PerViewReplies`, `BrowseType`, `IsBrowseSwitch`, `PostFormPosition`, `NumberOfLevels`, `IsDisplaySwitch`, `IsRatable`, `ViewingThreshold`, `IsOn`, `RootStylePrefix`, `BaseUrl`, `ObjectVote`, `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor`, `TriggerFieldTitle`, `TriggerFieldComments`, `ClassName`, `ClassFile`) VALUES

+ 2 - 2
modules/boonex/classes/install/sql/uninstall.sql

@@ -1,6 +1,6 @@
 
 -- TABLES
-DROP TABLE IF EXISTS `bx_classes_classes`, `bx_classes_covers`, `bx_classes_files`, `bx_classes_photos`, `bx_classes_photos_resized`, `bx_classes_videos`, `bx_classes_videos_resized`, `bx_classes_cmts`, `bx_classes_cmts_notes`, `bx_classes_votes`, `bx_classes_votes_track`, `bx_classes_reactions`, `bx_classes_reactions_track`, `bx_classes_views_track`, `bx_classes_meta_keywords`, `bx_classes_meta_locations`, `bx_classes_meta_mentions`, `bx_classes_reports`, `bx_classes_reports_track`, `bx_classes_favorites_track`, `bx_classes_scores`, `bx_classes_scores_track`, `bx_classes_polls`, `bx_classes_polls_answers`, `bx_classes_polls_answers_votes`, `bx_classes_polls_answers_votes_track`;
+DROP TABLE IF EXISTS `bx_classes_classes`, `bx_classes_modules`, `bx_classes_statuses`, `bx_classes_covers`, `bx_classes_files`, `bx_classes_photos`, `bx_classes_photos_resized`, `bx_classes_videos`, `bx_classes_videos_resized`, `bx_classes_cmts`, `bx_classes_cmts_notes`, `bx_classes_votes`, `bx_classes_votes_track`, `bx_classes_reactions`, `bx_classes_reactions_track`, `bx_classes_views_track`, `bx_classes_meta_keywords`, `bx_classes_meta_locations`, `bx_classes_meta_mentions`, `bx_classes_reports`, `bx_classes_reports_track`, `bx_classes_favorites_track`, `bx_classes_scores`, `bx_classes_scores_track`, `bx_classes_polls`, `bx_classes_polls_answers`, `bx_classes_polls_answers_votes`, `bx_classes_polls_answers_votes_track`;
 
 -- STORAGES & TRANSCODERS
 DELETE FROM `sys_objects_storage` WHERE `object` LIKE 'bx_classes_%';
@@ -19,7 +19,7 @@ DELETE FROM `sys_form_display_inputs` WHERE `display_name` LIKE 'bx_classes_%';
 -- PRE-VALUES
 DELETE FROM `sys_form_pre_lists` WHERE `module` = 'bx_classes';
 
-DELETE FROM `sys_form_pre_values` WHERE `Key` IN('bx_classes_avail', 'bx_classes_cmts');
+DELETE FROM `sys_form_pre_values` WHERE `Key` IN('bx_classes_avail', 'bx_classes_cmts', 'bx_classes_completed_when');
 
 -- CATEGORIES
 DELETE FROM `sys_categories` WHERE `module` = 'bx_classes';

+ 102 - 0
modules/boonex/classes/js/main.js

@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Classes Classes
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+function bx_classes_show_module_add_form(iProfileId)
+{
+    var sActionUrl = sUrlRoot + 'modules/index.php?r=classes/add_module/' + iProfileId;
+    $(window).dolPopupAjax({url: sActionUrl, closeOnOuterClick: false});
+}
+
+function bx_classes_reorder_classes(iProfileId, bEnable)
+{
+    var eCourse = jQuery('.bx-course-classes .bx-form-advanced-wrapper');
+    var eSortable = jQuery('.bx-course-classes .bx-form-input-wrapper-custom');
+    var sHandle = '<i class="handle sys-icon move bx-def-padding-right"></i>';
+    if (bEnable) {
+        jQuery('.bx-course-classes, .bx-course-controls').addClass('bx-course-reordering-mode');
+        if (!eCourse.find('.bx-classes-class-title .handle').size())
+            eSortable.find('.bx-classes-class-title').prepend(sHandle);
+        if (eSortable.hasClass('ui-sortable-disabled')) {
+            eSortable.sortable('enable');
+        }
+        else if (!eSortable.hasClass('ui-sortable')) {
+            eSortable.sortable({
+                items:'.bx-classes-class',
+                connectWith: ".bx-form-input-wrapper-custom",
+                handle: '.handle',
+                placeholder: 'bx-classes-placeholder sys-icon arrow-right',
+                forcePlaceholderSize: true, 
+                dropOnEmpty: true,
+                stop: function(oEvent, oUi) {
+                    var a = eCourse.find('.bx-form-section-wrapper > .bx-form-section'); 
+                    var s = '';
+                    var i = 0;
+                    $.each(a, function () {
+                        var aa = $(this).find('.bx-classes-class');
+                        var iModuleId = $(this).attr('id').replace('module_', '');
+                        s += '&classes_order_' +iModuleId + '=';
+                        $.each(aa, function () {
+                            s += $(this).attr('id').replace('class_', '') + ',';
+                        });
+                        s = s.replace(/,$/, '');
+                    });
+                    var sActionUrl = sUrlRoot + 'modules/index.php?r=classes/reorder_classes/' + iProfileId + s;
+                    $.post(sActionUrl);
+                }
+
+            });
+        }
+        glBxClassesSortableClassesInitialized = true;
+    }
+    else if (!bEnable && 'undefined' !== typeof(glBxClassesSortableClassesInitialized)) {
+        eSortable.sortable('disable');
+        eSortable.find('.bx-classes-class .handle').remove();
+        jQuery('.bx-course-classes, .bx-course-controls').removeClass('bx-course-reordering-mode');
+    }
+}
+
+function bx_classes_reorder_modules(iProfileId, bEnable)
+{
+    var eCourse = jQuery('.bx-course-classes .bx-form-advanced-wrapper');
+    var sHandle = '<i class="handle sys-icon move bx-def-padding-leftright"></i>';
+    if (bEnable) {
+        jQuery('.bx-course-classes, .bx-course-controls').addClass('bx-course-reordering-mode');
+        if (!eCourse.find('legend .handle').size())
+            eCourse.find('legend').prepend(sHandle);
+        if (eCourse.hasClass('ui-sortable-disabled')) {
+            eCourse.sortable('enable');
+        }
+        else if (!eCourse.hasClass('ui-sortable')) {
+            eCourse.sortable({
+                items:'.bx-form-section-wrapper',
+                handle: '.handle',
+                stop: function(oEvent, oUi) {
+                    var a = eCourse.find('.bx-form-section-wrapper > .bx-form-section'); 
+                    var s = '';
+                    var i = 0;
+                    $.each(a, function () {                        
+                        s += '&modules_order[]=' + $(this).attr('id').replace('module_', '');
+                    });
+                    var sActionUrl = sUrlRoot + 'modules/index.php?r=classes/reorder_modules/' + iProfileId + s;
+                    $.post(sActionUrl);
+                }
+
+            });
+        }
+        glBxClassesSortableModulesInitialized = true;
+    }
+    else if (!bEnable && 'undefined' !== typeof(glBxClassesSortableModulesInitialized)) {
+        eCourse.sortable('disable');
+        eCourse.find('legend .handle').remove();
+        jQuery('.bx-course-classes, .bx-course-controls').removeClass('bx-course-reordering-mode');
+    }
+}
+
+/** @} */

+ 31 - 0
modules/boonex/classes/template/classes_add_module_form.html

@@ -0,0 +1,31 @@
+<div id="bx-classes-add-module-form" class="bx-classes-add-page-form">
+    __form__
+</div>
+<script>
+    $(document).ready(function () {
+        $("#bx-classes-add-module-form form").ajaxForm({
+            url: sUrlRoot + 'modules/index.php?r=classes/add_module/__profile_id__',
+            beforeSubmit: function (formData, $form, options) {
+                bx_loading($("#bx-classes-add-module-form form"), true);
+            },
+            success: function (data) {
+                if ('{' == data[0]) {
+                    var oResponce = JSON.parse(data);
+                    if ('ReloadLessonsAndClosePopup' == oResponce.action) {
+                        $('.bx-popup-applied:visible').dolPopupHide();
+                        loadDynamicBlockAuto('.bx-course-controls');
+                    } 
+                    else if ('ShowMsg' == oResponce.action) {
+                        bx_alert(oResponce.msg);
+                    }
+                    else {
+                        bx_alert('<bx_text_js:_sys_txt_error_occured />');
+                    }
+                }
+                else {
+                    $("#bx-classes-add-module-form").parent().html(data).bxProcessHtml();
+                }
+            }
+        });
+    });
+</script>

+ 28 - 0
modules/boonex/classes/template/classes_class_row.html

@@ -0,0 +1,28 @@
+<div id="class___id__" class="bx-classes-class bx-def-margin-thd-topbottom">
+    <div class="bx-classes-class-title bx-def-font-h3 __status__">
+        <bx_if:completed>
+            <i class="sys-icon check-circle col-green1-dark sys-colored"></i>
+            <a href="__url__">__title__</a>
+            <span class="bx-def-font-small bx-def-font-grayed">__date_created__</span>
+            <span class="bx-def-font-small col-green1-dark">__start_date__</span>
+            <span class="bx-def-font-small col-red1">__end_date__</span>
+        </bx_if:completed>
+        <bx_if:avail>
+            <i class="sys-icon play-circle col-blue1 sys-colored"></i>
+            <a href="__url__">__title__</a>
+            <span class="bx-def-font-small bx-def-font-grayed">__date_created__</span>
+            <span class="bx-def-font-small col-green1-dark">__start_date__</span>
+            <span class="bx-def-font-small col-red1">__end_date__</span>
+        </bx_if:avail>
+        <bx_if:na>
+            <i class="sys-icon ban col-red1 sys-colored"></i>
+            <b title="__tip__" class="bx-def-font-grayed">
+                __title__ 
+                <i class="sys-icon info-circle"></i>
+            </b>
+            <span class="bx-def-font-small bx-def-font-grayed">__date_created__</span>
+            <span class="bx-def-font-small col-green1-dark">__start_date__</span>
+            <span class="bx-def-font-small col-red1">__end_date__</span>
+        </bx_if:na>
+    </div>
+</div>

+ 16 - 0
modules/boonex/classes/template/classes_in_context.html

@@ -0,0 +1,16 @@
+<bx_if:admin>
+<div class="bx-course-controls bx-def-margin-bottom bx-clearfix">
+    <div class="bx-course-controls-1 bx-clearfix">
+        <a class="bx-btn bx-def-margin-right" href="__new_class_url__">Add Class</a>
+        <a class="bx-btn" href="javascript:void(0);" onclick="bx_classes_show_module_add_form('__context_profile_id__')">Add Module</a>
+    </div>
+    <div class="bx-course-controls-2 bx-clearfix">
+        <a class="bx-btn" href="javascript:void(0);" onclick="bx_classes_reorder_modules('__context_profile_id__', true)">Rearrange Moodules</a>
+        <a class="bx-btn bx-def-margin-left" href="javascript:void(0);" onclick="bx_classes_reorder_classes('__context_profile_id__', true)">Rearrange Classes</a>
+        <a class="bx-btn bx-def-margin-left bx-classes-btn-done" href="javascript:void(0);" onclick="bx_classes_reorder_classes('__context_profile_id__', false); bx_classes_reorder_modules('__context_profile_id__', false);">Done</a>
+    </div>
+</div>
+</bx_if:admin>
+<div class="bx-course-classes">
+    __form__
+</div>

+ 30 - 0
modules/boonex/classes/template/css/main.css

@@ -1 +1,31 @@
 @import url(../../../../base/text/template/css/main.css);
+
+.bx-course-controls-1 {
+    float:left;
+}
+
+.bx-course-controls-2 {
+    float:right;
+}
+
+.bx-course-classes .bx-form-input-wrapper-custom {
+    min-height:1rem;
+}
+
+html .bx-classes-class-title {
+    line-height:2rem;
+}
+
+.bx-classes-btn-done {
+    display:none;
+}
+.bx-course-reordering-mode .bx-classes-btn-done {
+    display:block;
+}
+.bx-course-reordering-mode .bx-btn:not(.bx-classes-btn-done) {
+    display:none;
+}
+
+.bx-classes-class-status-completed a {
+    text-decoration: line-through;
+}

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

@@ -51,12 +51,14 @@
     <string name="_feature_err_duplicate_feature"><![CDATA[The item was already added in featured list.]]></string>
     <string name="_feature_err_not_enabled"><![CDATA[The feature is not available right now. Please, try again later.]]></string>
 	<string name="_Female"><![CDATA[Woman]]></string>
+        <string name="_free"><![CDATA[free]]></string>
 	<string name="_From"><![CDATA[From]]></string>
 	<string name="_Header"><![CDATA[Header]]></string>
 	<string name="_Incorrect Captcha"><![CDATA[Incorrect Captcha]]></string>
 	<string name="_Incorrect Email"><![CDATA[Incorrect Email]]></string>
 	<string name="_Info"><![CDATA[Info]]></string>
 	<string name="_Keyword"><![CDATA[Keyword]]></string>
+        <string name="_lifetime"><![CDATA[lifetime]]></string>
 	<string name="_LookinFemale"><![CDATA[Woman]]></string>
 	<string name="_LookinMale"><![CDATA[Man]]></string>
 	<string name="_MEMBERSHIP_EXPIRES_IN_DAYS"><![CDATA[expires: in {0} day(s)]]></string>
@@ -2496,6 +2498,7 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_menu_item_title_sm_unfriend"><![CDATA[Remove friend]]></string>
     <string name="_sys_menu_item_title_sm_unfriend_cancel"><![CDATA[Cancel friend request]]></string>
     <string name="_sys_menu_item_title_sm_unfriend_reject"><![CDATA[Reject friend request]]></string>
+    <string name="_sys_menu_item_title_sm_join_paid"><![CDATA[Pay & Join]]></string>
     <string name="_sys_menu_item_title_sm_join"><![CDATA[Join]]></string>
     <string name="_sys_menu_item_title_sm_join_confirm"><![CDATA[Accept join request]]></string>
     <string name="_sys_menu_item_title_sm_leave"><![CDATA[Leave]]></string>
@@ -2581,6 +2584,7 @@ If it is not enabled then please consider implement this optimization, since it
     <string name="_sys_menu_item_title_system_sm_actions"><![CDATA[Actions]]></string>
     <string name="_sys_menu_item_title_system_sm_befriend"><![CDATA[Add Friend]]></string>
     <string name="_sys_menu_item_title_system_sm_unfriend"><![CDATA[Remove Friend]]></string>
+    <string name="_sys_menu_item_title_system_sm_join_paid"><![CDATA[Pay & Join]]></string>
     <string name="_sys_menu_item_title_system_sm_join"><![CDATA[Join]]></string>
     <string name="_sys_menu_item_title_system_sm_leave"><![CDATA[Leave]]></string>
     <string name="_sys_menu_item_title_system_sm_nl"><![CDATA[New Line]]></string>
@@ -3045,6 +3049,7 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_txt_login_invalid_role"><![CDATA[Access denied]]></string>
     <string name="_sys_txt_my_profiles"><![CDATA[Create a new profile]]></string>
     <string name="_sys_txt_new_picture"><![CDATA[New Picture]]></string>
+    <string name="_sys_txt_not_found"><![CDATA[Not found]]></string>
     <string name="_sys_txt_no_picture_uploaded"><![CDATA[No picture uploaded]]></string>
     <string name="_sys_txt_no_title"><![CDATA[No title]]></string>
     <string name="_sys_txt_n_by"><![CDATA[{0} by]]></string>

+ 27 - 10
modules/boonex/groups/classes/BxGroupsConfig.php

@@ -33,6 +33,7 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
             'TABLE_ENTRIES_FULLTEXT' => 'search_fields',
             'TABLE_ADMINS' => $aModule['db_prefix'] . 'admins',
             'TABLE_INVITES' => $aModule['db_prefix'] . 'invites',
+            'TABLE_PRICES' => $aModule['db_prefix'] . 'prices',
 
             // database fields
             'FIELD_ID' => 'id',
@@ -59,6 +60,7 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
             'URI_VIEW_ENTRY' => 'view-group-profile',
             'URI_EDIT_ENTRY' => 'edit-group-profile',
             'URI_EDIT_COVER' => 'edit-group-cover',
+            'URI_JOIN_ENTRY' => 'join-group-profile',
             'URI_JOINED_ENTRIES' => 'joined-groups',
             'URI_MANAGE_COMMON' => 'groups-manage',
 
@@ -73,6 +75,8 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
             'PARAM_SEARCHABLE_FIELDS' => 'bx_groups_searchable_fields',
             'PARAM_PER_PAGE_BROWSE_SHOWCASE' => 'bx_groups_per_page_browse_showcase',
             'PARAM_PER_PAGE_BROWSE_RECOMMENDED' => 'bx_groups_per_page_browse_recommended',
+            'PARAM_PER_PAID_JOIN' => 'bx_groups_enable_paid_join',
+            'PARAM_RECURRING_RESERVE' => 3, // 3 days for recurring payment to be registered
 
             // objects
             'OBJECT_STORAGE' => 'bx_groups_pics',
@@ -102,6 +106,9 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
             'OBJECT_FORM_ENTRY_DISPLAY_EDIT_COVER' => 'bx_group_edit_cover',
             'OBJECT_FORM_ENTRY_DISPLAY_DELETE' => 'bx_group_delete',
             'OBJECT_FORM_ENTRY_DISPLAY_INVITE' => 'bx_group_invite',
+            'OBJECT_FORM_PRICE' => 'bx_groups_price',
+            'OBJECT_FORM_PRICE_DISPLAY_ADD' => 'bx_groups_price_add',
+            'OBJECT_FORM_PRICE_DISPLAY_EDIT' => 'bx_groups_price_edit',
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_groups_view_actions', // actions menu on view entry page
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY_MORE' => 'bx_groups_view_actions_more', // actions menu on view entry page for "more" popup
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_groups_view_actions_all', // all actions menu on view entry page
@@ -121,10 +128,13 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
             'OBJECT_GRID_COMMON' => 'bx_groups_common',
             'OBJECT_GRID_CONNECTIONS' => 'bx_groups_fans',
             'OBJECT_GRID_INVITES' => 'bx_groups_invites',
+            'OBJECT_GRID_PRICES_MANAGE' => 'bx_groups_prices_manage',
+            'OBJECT_GRID_PRICES_VIEW' => 'bx_groups_prices_view',
             'OBJECT_CONNECTIONS' => 'bx_groups_fans',
             'OBJECT_UPLOADERS_COVER' => array('bx_groups_cover_crop'),
             'OBJECT_UPLOADERS_PICTURE' => array('bx_groups_picture_crop'),
             'OBJECT_PRE_LIST_ROLES' => 'bx_groups_roles',
+            'OBJECT_FORM_PRELISTS_PERIOD_UNITS' => 'bx_groups_period_units',
             
             'BADGES_AVALIABLE' => true,
             
@@ -196,11 +206,17 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
                 'menu_item_title_leave_group' => '_bx_groups_menu_item_title_leave_group',
                 'txt_all_entries_by_author' => '_bx_groups_page_title_browse_by_author',
                 'txt_invitation_popup_title' => '_bx_groups_txt_invite_popup_title',
-				'txt_invitation_popup_text' => '_bx_groups_txt_invite_popup_text',
-				'txt_invitation_popup_accept_button' => '_bx_groups_txt_invite_popup_button_accept',
-				'txt_invitation_popup_decline_button' => '_bx_groups_txt_invite_popup_button_decline',
-				'txt_invitation_popup_error_invitation_absent' => '_bx_groups_txt_invite_popup_error_invitation_absent',
-				'txt_invitation_popup_error_wrong_user' => '_bx_groups_txt_invite_popup_error_invitation_wrong_user',
+                'txt_invitation_popup_text' => '_bx_groups_txt_invite_popup_text',
+                'txt_invitation_popup_accept_button' => '_bx_groups_txt_invite_popup_button_accept',
+                'txt_invitation_popup_decline_button' => '_bx_groups_txt_invite_popup_button_decline',
+                'txt_invitation_popup_error_invitation_absent' => '_bx_groups_txt_invite_popup_error_invitation_absent',
+                'txt_invitation_popup_error_wrong_user' => '_bx_groups_txt_invite_popup_error_invitation_wrong_user',
+                'txt_n_unit' => '_bx_groups_txt_n_unit',
+                'txt_buy_title' => '_bx_groups_grid_action_title_buy_title',
+                'txt_cart_item_title' => '_bx_groups_txt_cart_item_title',
+                'txt_subscribe_title' => '_bx_groups_grid_action_title_subscribe_title',
+                'msg_performed' => '_bx_groups_msg_performed',
+                'err_cannot_perform' => '_bx_groups_err_cannot_perform',
             ),
 
         );
@@ -208,19 +224,20 @@ class BxGroupsConfig extends BxBaseModGroupsConfig
         $this->_aJsClasses = array(
             'main' => 'BxGroupsMain',
             'manage_tools' => 'BxGroupsManageTools',
-            'invite_popup' => 'BxGroupsInvitePopup'
+            'invite_popup' => 'BxGroupsInvitePopup',
+            'prices' => 'BxGroupsPrices'
         );
 
         $this->_aJsObjects = array(
             'main' => 'oBxGroupsMain',
             'manage_tools' => 'oBxGroupsManageTools',
-            'invite_popup' => 'oBxGroupsInvitePopup'
+            'invite_popup' => 'oBxGroupsInvitePopup',
+            'prices' => 'oBxGroupsPrices'
         );
 
         $this->_aGridObjects = array(
-        	'common' => $this->CNF['OBJECT_GRID_COMMON'],
-        	'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
-        	
+            'common' => $this->CNF['OBJECT_GRID_COMMON'],
+            'administration' => $this->CNF['OBJECT_GRID_ADMINISTRATION'],
         );
     }
 

+ 22 - 0
modules/boonex/groups/classes/BxGroupsCronPruning.php

@@ -0,0 +1,22 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxGroupsCronPruning extends BxBaseModGroupsCronPruning
+{
+    public function __construct()
+    {
+        $this->_sModule = 'bx_groups';
+
+        parent::__construct();
+    }
+}
+
+/** @} */

+ 22 - 0
modules/boonex/groups/classes/BxGroupsGridPricesManage.php

@@ -0,0 +1,22 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxGroupsGridPricesManage extends BxBaseModGroupsGridPricesManage
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        $this->_sModule = 'bx_groups';
+
+        parent::__construct ($aOptions, $oTemplate);
+    }
+}
+
+/** @} */

+ 7 - 5
modules/boonex/tasks/classes/BxTasksGridAdministration.php → modules/boonex/groups/classes/BxGroupsGridPricesView.php

@@ -1,18 +1,20 @@
 <?php defined('BX_DOL') or die('hack attempt');
 /**
  * Copyright (c) UNA, Inc - https://una.io
- * MIT License - https://opensource.org/licenses/MIT 
- * @defgroup    Tasks Tasks
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
  * @ingroup     UnaModules
- * 
+ *
  * @{
  */
 
-class BxTasksGridAdministration extends BxBaseModTextGridAdministration
+class BxGroupsGridPricesView extends BxBaseModGroupsGridPricesView
 {
     public function __construct ($aOptions, $oTemplate = false)
     {
-    	$this->MODULE = 'bx_tasks';
+        $this->_sModule = 'bx_groups';
+
         parent::__construct ($aOptions, $oTemplate);
     }
 }

+ 10 - 0
modules/boonex/groups/classes/BxGroupsMenuViewActionsAll.php

@@ -21,6 +21,11 @@ class BxGroupsMenuViewActionsAll extends BxBaseModGroupsMenuViewActionsAll
         parent::__construct($aObject, $oTemplate);
     }
 
+    protected function _getMenuItemJoinGroupProfile($aItem)
+    {
+        return $this->_getMenuItemByNameActions($aItem);
+    }
+
     protected function _getMenuItemEditGroupCover($aItem)
     {
         return $this->_getMenuItemByNameActionsMore($aItem);
@@ -31,6 +36,11 @@ class BxGroupsMenuViewActionsAll extends BxBaseModGroupsMenuViewActionsAll
         return $this->_getMenuItemByNameActionsMore($aItem);
     }
 
+    protected function _getMenuItemEditGroupPricing($aItem)
+    {
+        return $this->_getMenuItemByNameActionsMore($aItem);
+    }
+
     protected function _getMenuItemInviteToGroup($aItem)
     {
         return $this->_getMenuItemByNameActionsMore($aItem);

+ 17 - 0
modules/boonex/groups/install/installer.php

@@ -15,6 +15,23 @@ class BxGroupsInstaller extends BxBaseModProfileInstaller
     {
         parent::__construct($aConfig);
     }
+
+    function enable($aParams)
+    {
+        $aResult = parent::enable($aParams);
+
+        if($aResult['result'])
+            BxDolPayments::getInstance()->updateDependentModules($this->_aConfig['name'], true);
+
+        return $aResult;
+    }
+
+    function disable($aParams)
+    {
+        BxDolPayments::getInstance()->updateDependentModules($this->_aConfig['name'], false);
+
+        return parent::disable($aParams);
+    }
 }
 
 /** @} */

+ 67 - 3
modules/boonex/groups/install/langs/en.xml

@@ -8,6 +8,7 @@
 	<string name="_bx_groups_option_per_page_browse"><![CDATA[Number of items per page]]></string>
     <string name="_bx_groups_option_num_rss"><![CDATA[Number of items in RSS feed]]></string>
     <string name="_bx_groups_option_searchable_fields"><![CDATA[Searchable fields]]></string>
+    <string name="_bx_groups_option_enable_paid_join"><![CDATA[Enable 'Paid Join']]></string>
 
 	<string name="_bx_groups_form_profile"><![CDATA[Groups]]></string>
 	<string name="_bx_groups_form_profile_display_add"><![CDATA[Add Group]]></string>
@@ -46,6 +47,26 @@
 	<string name="_bx_groups_form_profile_input_delete_confirm_info"><![CDATA[This action can not be undone.]]></string>
 	<string name="_bx_groups_form_profile_input_delete_confirm_error"><![CDATA[Please check this checkbox if you really want to delete group.]]></string>
 
+    <string name="_bx_groups_form_price"><![CDATA[Price]]></string>
+    <string name="_bx_groups_form_price_display_add"><![CDATA[Add]]></string>
+    <string name="_bx_groups_form_price_display_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_groups_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_groups_form_price_input_sys_role_id"><![CDATA[Role ID]]></string>
+    <string name="_bx_groups_form_price_input_sys_period"><![CDATA[Period]]></string>
+    <string name="_bx_groups_form_price_input_period"><![CDATA[Period]]></string>
+    <string name="_bx_groups_form_price_input_inf_period"><![CDATA[Use 0 for lifetime]]></string>
+    <string name="_bx_groups_form_price_input_sys_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_groups_form_price_input_period_unit"><![CDATA[Period unit]]></string>
+    <string name="_bx_groups_form_price_input_inf_period_unit"><![CDATA[Leave empty for lifetime level.]]></string>
+    <string name="_bx_groups_form_price_input_err_period_unit"><![CDATA[You need to select a period unit here.]]></string>
+    <string name="_bx_groups_form_price_input_sys_price"><![CDATA[Price]]></string>
+    <string name="_bx_groups_form_price_input_price"><![CDATA[Price]]></string>
+    <string name="_bx_groups_form_price_input_inf_price"><![CDATA[Use 0 or leave empty to create free level.]]></string>
+    <string name="_bx_groups_form_price_input_sys_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_groups_form_price_input_do_submit"><![CDATA[Submit]]></string>
+    <string name="_bx_groups_form_price_input_sys_do_cancel"><![CDATA[Cancel]]></string>
+    <string name="_bx_groups_form_price_input_do_cancel"><![CDATA[Cancel]]></string>
+
 	<string name="_bx_groups_acl_action_create_profile"><![CDATA[Create Group]]></string>
 	<string name="_bx_groups_acl_action_delete_profile"><![CDATA[Delete Group]]></string>
 	<string name="_bx_groups_acl_action_view_profile"><![CDATA[View Group]]></string>
@@ -62,6 +83,8 @@
 	<string name="_bx_groups_page_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
 	<string name="_bx_groups_page_title_sys_delete_profile"><![CDATA[Delete Group]]></string>
 	<string name="_bx_groups_page_title_delete_profile"><![CDATA[Delete Group]]></string>
+        <string name="_bx_groups_page_title_sys_join_profile"><![CDATA[Join Group]]></string>
+	<string name="_bx_groups_page_title_join_profile"><![CDATA[Join Group]]></string>
 	<string name="_bx_groups_page_title_sys_view_profile_closed"><![CDATA[View Closed Group]]></string>
 	<string name="_bx_groups_page_title_sys_view_profile"><![CDATA[View Group]]></string>
 	<string name="_bx_groups_page_title_view_profile"><![CDATA[{display_name}]]></string>
@@ -80,6 +103,8 @@
     <string name="_bx_groups_page_title_profile_comments"><![CDATA[Comments]]></string>
 	<string name="_bx_groups_page_title_sys_profile_info"><![CDATA[Group Info]]></string>
 	<string name="_bx_groups_page_title_profile_info"><![CDATA[Info about {display_name}]]></string>
+        <string name="_bx_groups_page_title_sys_profile_pricing"><![CDATA[Pricing]]></string>
+	<string name="_bx_groups_page_title_profile_pricing"><![CDATA[Pricing]]></string>
 	<string name="_bx_groups_page_title_sys_recent"><![CDATA[New Groups]]></string>
 	<string name="_bx_groups_page_title_recent"><![CDATA[New Groups]]></string>
 	<string name="_bx_groups_page_title_sys_manage"><![CDATA[Manage Own Groups]]></string>
@@ -106,9 +131,13 @@
 	<string name="_bx_groups_page_block_title_edit_profile"><![CDATA[Edit Group]]></string>
 	<string name="_bx_groups_page_block_title_edit_profile_cover"><![CDATA[Edit Cover]]></string>
 	<string name="_bx_groups_page_block_title_delete_profile"><![CDATA[Delete Group]]></string>
+        <string name="_bx_groups_page_block_title_join_profile"><![CDATA[Join Group]]></string>
 	<string name="_bx_groups_page_block_title_profile_info_link"><![CDATA[About <a href="{profile_link}">{display_name}</a>]]></string>
 	<string name="_bx_groups_page_block_title_profile_info"><![CDATA[Info]]></string>
 	<string name="_bx_groups_page_block_title_system_profile_info"><![CDATA[Info]]></string>
+        <string name="_bx_groups_page_block_title_system_profile_pricing"><![CDATA[Pricing]]></string>
+        <string name="_bx_groups_page_block_title_profile_pricing"><![CDATA[Pricing]]></string>
+        <string name="_bx_groups_page_block_title_profile_pricing_link"><![CDATA[Pricing of <a href="{profile_link}">{display_name}</a>]]></string>
 	<string name="_bx_groups_page_block_title_profile_description"><![CDATA[Description]]></string>
 	<string name="_bx_groups_page_block_title_profile_friends_link"><![CDATA[Friends of <a href="{profile_link}">{display_name}</a>]]></string>
 	<string name="_bx_groups_page_block_title_profile_friends"><![CDATA[Friends]]></string>
@@ -169,6 +198,8 @@
     <string name="_bx_groups_menu_item_title_delete_with_content"><![CDATA[Delete With Content]]></string>
     <string name="_bx_groups_menu_item_title_system_edit_cover"><![CDATA[Edit Cover]]></string>
     <string name="_bx_groups_menu_item_title_edit_cover"><![CDATA[Edit Cover]]></string>
+    <string name="_bx_groups_menu_item_title_system_edit_pricing"><![CDATA[Edit Pricing]]></string>
+    <string name="_bx_groups_menu_item_title_edit_pricing"><![CDATA[Edit Pricing]]></string>
     <string name="_bx_groups_menu_item_title_system_edit_profile"><![CDATA[Edit Group]]></string>
     <string name="_bx_groups_menu_item_title_edit_profile"><![CDATA[Edit Group]]></string>
     <string name="_bx_groups_menu_item_title_system_delete_profile"><![CDATA[Delete Group]]></string>
@@ -214,6 +245,9 @@
     <string name="_bx_groups_menu_item_title_leave_group_cancel_request"><![CDATA[Cancel Request]]></string>
     <string name="_bx_groups_menu_item_title_leave_group_reject_request"><![CDATA[Reject Request]]></string>
 
+    <string name="_bx_groups_menu_item_title_system_pay_and_join"><![CDATA[Pay & Join]]></string>
+    <string name="_bx_groups_menu_item_title_pay_and_join"><![CDATA[Pay & Join]]></string>
+
     <string name="_bx_groups_menu_item_title_system_subscribe"><![CDATA[Follow]]></string>
     <string name="_bx_groups_menu_item_title_subscribe"><![CDATA[Follow]]></string>
     <string name="_bx_groups_menu_item_title_system_unsubscribe"><![CDATA[Unfollow]]></string>
@@ -256,8 +290,24 @@
     <string name="_bx_groups_grid_action_title_adm_delete"><![CDATA[Delete]]></string>
     <string name="_bx_groups_grid_action_title_adm_delete_with_content"><![CDATA[Delete With Content]]></string>
     <string name="_bx_groups_grid_action_title_adm_more_actions"><![CDATA[More Actions]]></string>
-	<string name="_bx_groups_grid_action_err_delete"><![CDATA[Cannot delete the selected group(s).]]></string>
-	<string name="_bx_groups_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+    <string name="_bx_groups_grid_action_err_delete"><![CDATA[Cannot delete the selected group(s).]]></string>
+    <string name="_bx_groups_grid_txt_account_manager"><![CDATA[Redirect to manage account.]]></string>
+
+    <string name="_bx_groups_grid_column_title_name"><![CDATA[Name]]></string>
+    <string name="_bx_groups_grid_column_title_role_id"><![CDATA[Role]]></string>
+    <string name="_bx_groups_grid_column_title_period"><![CDATA[Period]]></string>
+    <string name="_bx_groups_grid_column_title_price"><![CDATA[Price]]></string>
+    <string name="_bx_groups_grid_action_title_add"><![CDATA[Add]]></string>
+    <string name="_bx_groups_grid_action_title_edit"><![CDATA[Edit]]></string>
+    <string name="_bx_groups_grid_action_title_delete"><![CDATA[Delete]]></string>
+    <string name="_bx_groups_grid_action_title_buy"><![CDATA[Buy]]></string>
+    <string name="_bx_groups_grid_action_title_buy_title"><![CDATA[One-time payment]]></string>
+    <string name="_bx_groups_grid_action_title_subscribe"><![CDATA[Rent]]></string>
+    <string name="_bx_groups_grid_action_title_subscribe_title"><![CDATA[Subscribe on recurring payments]]></string>
+    <string name="_bx_groups_grid_action_title_choose"><![CDATA[Choose]]></string>
+    <string name="_bx_groups_grid_action_title_choose_title"><![CDATA[Switch to the level for free]]></string>
+    <string name="_bx_groups_popup_title_price_add"><![CDATA[Add Pricing Plan]]></string>
+    <string name="_bx_groups_popup_title_price_edit"><![CDATA[Edit Pricing Plan]]></string>
 
     <string name="_bx_groups_chart_growth"><![CDATA[Groups: Growth]]></string>
     <string name="_bx_groups_chart_growth_speed"><![CDATA[Groups: Growth Speed]]></string>
@@ -269,6 +319,8 @@
     <string name="_bx_groups_txt_account_suspended"><![CDATA[This group is suspended.]]></string>
     <string name="_bx_groups_txt_N_fans"><![CDATA[{0} members]]></string>
     <string name="_bx_groups_txt_role"><![CDATA[Role]]></string>
+    <string name="_bx_groups_txt_role_added"><![CDATA[Added]]></string>
+    <string name="_bx_groups_txt_role_expired"><![CDATA[Expires]]></string>
     <string name="_bx_groups_txt_sample_single"><![CDATA[group]]></string>
     <string name="_bx_groups_txt_sample_single_with_article"><![CDATA[a group]]></string>
     <string name="_bx_groups_txt_sample_comment_single"><![CDATA[comment to group]]></string>
@@ -283,8 +335,14 @@
     <string name="_bx_groups_txt_invite_popup_button_decline"><![CDATA[Decline]]></string>
     <string name="_bx_groups_txt_invite_popup_error_invitation_absent"><![CDATA[Sorry, an error has occurred. You can not join this group. The invitation is expired or deleted.]]></string>
     <string name="_bx_groups_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Sorry, an error has occurred. You can not join this group. This invitation cannot be used by you.]]></string>
+    <string name="_bx_groups_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_groups_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
 
-	  <string name="_bx_groups_alert_action_fan_added"><![CDATA[member joined]]></string>
+    <string name="_bx_groups_msg_performed"><![CDATA[The action was successfully performed.]]></string>
+    <string name="_bx_groups_err_price_duplicate"><![CDATA[Similar pricing plan is already exists.]]></string>
+    <string name="_bx_groups_err_cannot_perform"><![CDATA[An error appeared. Please report.]]></string>
+
+    <string name="_bx_groups_alert_action_fan_added"><![CDATA[member joined]]></string>
     <string name="_bx_groups_txt_ntfs_fan_added"><![CDATA[Group <b class="bx-ntfs-content-profile">{owner_name}</b> has got a member <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
     <string name="_bx_groups_alert_action_join_request"><![CDATA[join request]]></string>
     <string name="_bx_groups_txt_ntfs_join_request"><![CDATA[Group <b class="bx-ntfs-content-profile">{owner_name}</b> has a join request from <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
@@ -303,6 +361,12 @@
     <string name="_bx_groups_role_administrator"><![CDATA[Administrator]]></string>
     <string name="_bx_groups_role_moderator"><![CDATA[Moderator]]></string>
 
+    <string name="_bx_groups_pre_lists_period_units"><![CDATA[Billing Interval]]></string>
+    <string name="_bx_groups_period_unit_day"><![CDATA[day]]></string>
+    <string name="_bx_groups_period_unit_week"><![CDATA[week]]></string>
+    <string name="_bx_groups_period_unit_month"><![CDATA[month]]></string>
+    <string name="_bx_groups_period_unit_year"><![CDATA[year]]></string>
+
     <string name="_bx_groups_email_join_request"><![CDATA[Group join request]]></string>
     <string name="_bx_groups_email_join_request_subject"><![CDATA[Request to join your group]]></string>
     <string name="_bx_groups_email_join_request_body"><![CDATA[{email_header}

+ 7 - 4
modules/boonex/groups/install/sql/disable.sql

@@ -8,7 +8,7 @@ DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
 
 -- PAGES
 DELETE FROM `sys_objects_page` WHERE `module` = 'bx_groups';
-DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_groups' OR `object` IN('bx_groups_create_profile', 'bx_groups_delete_profile', 'bx_groups_edit_profile', 'bx_groups_edit_profile_cover', 'bx_groups_invite', 'bx_groups_view_profile', 'bx_groups_view_profile_closed', 'bx_groups_profile_info', 'bx_groups_profile_comments', 'bx_groups_home', 'bx_groups_fans', 'bx_groups_joined', 'bx_groups_top', 'bx_groups_search', 'bx_groups_manage', 'bx_groups_administration');
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_groups' OR `object` IN('bx_groups_create_profile', 'bx_groups_delete_profile', 'bx_groups_join_profile', 'bx_groups_edit_profile', 'bx_groups_edit_profile_cover', 'bx_groups_invite', 'bx_groups_view_profile', 'bx_groups_view_profile_closed', 'bx_groups_profile_info', 'bx_groups_profile_pricing', 'bx_groups_profile_comments', 'bx_groups_home', 'bx_groups_fans', 'bx_groups_joined', 'bx_groups_top', 'bx_groups_search', 'bx_groups_manage', 'bx_groups_administration');
 
 -- MENU
 DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_groups';
@@ -38,9 +38,9 @@ DELETE FROM `sys_statistics` WHERE `name` LIKE 'bx_groups%';
 DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_groups%';
 
 -- GRIDS
-DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites');
-DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites');
-DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites');
+DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites', 'bx_groups_prices_manage', 'bx_groups_prices_view');
+DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites', 'bx_groups_prices_manage', 'bx_groups_prices_view');
+DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_groups_administration', 'bx_groups_moderation', 'bx_groups_common', 'bx_groups_fans', 'bx_groups_invites', 'bx_groups_prices_manage', 'bx_groups_prices_view');
 
 -- ALERTS
 SET @iHandler := (SELECT `id` FROM `sys_alerts_handlers` WHERE `name` = 'bx_groups' LIMIT 1);
@@ -55,3 +55,6 @@ DELETE FROM `sys_email_templates` WHERE `Module` = 'bx_groups';
 
 -- UPLOADERS
 DELETE FROM `sys_objects_uploader` WHERE `object` IN ('bx_groups_cover_crop', 'bx_groups_picture_crop');
+
+-- CRON
+DELETE FROM `sys_cron_jobs` WHERE `name` IN ('bx_groups_pruning');

+ 77 - 25
modules/boonex/groups/install/sql/enable.sql

@@ -16,7 +16,8 @@ INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `e
 ('bx_groups_num_rss', '10', @iCategId, '_bx_groups_option_num_rss', 'digit', '', '', '', 12),
 ('bx_groups_per_page_browse_showcase', '32', @iCategId, '_sys_option_per_page_browse_showcase', 'digit', '', '', '', 15),
 ('bx_groups_per_page_browse_recommended', '10', @iCategId, '_sys_option_per_page_browse_recommended', 'digit', '', '', '', 16),
-('bx_groups_searchable_fields', 'group_name,group_desc', @iCategId, '_bx_groups_option_searchable_fields', 'list', 'a:2:{s:6:"module";s:9:"bx_groups";s:6:"method";s:21:"get_searchable_fields";}', '', '', 30);
+('bx_groups_searchable_fields', 'group_name,group_desc', @iCategId, '_bx_groups_option_searchable_fields', 'list', 'a:2:{s:6:"module";s:9:"bx_groups";s:6:"method";s:21:"get_searchable_fields";}', '', '', 30),
+('bx_groups_enable_paid_join', 'on', @iCategId, '_bx_groups_option_enable_paid_join', 'checkbox', '', '', '', 40);
 
 -- PAGES
 
@@ -85,8 +86,14 @@ INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module
 INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
 ('bx_groups_delete_profile', 1, 'bx_groups', '_bx_groups_page_block_title_delete_profile', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_groups\";s:6:\"method\";s:13:\"entity_delete\";}', 0, 0, 0);
 
--- PAGE: profile info
+-- PAGE: join profile
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_groups_join_profile', 'join-group-profile', '_bx_groups_page_title_sys_join_profile', '_bx_groups_page_title_join_profile', 'bx_groups', 5, 2147483647, 1, 'page.php?i=join-group-profile', '', '', '', 0, 1, 0, 'BxGroupsPageEntry', 'modules/boonex/groups/classes/BxGroupsPageEntry.php');
 
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
+('bx_groups_join_profile', 1, 'bx_groups', '_bx_groups_page_block_title_join_profile', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:11:"entity_join";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 0);
+
+-- PAGE: profile info
 INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
 ('bx_groups_profile_info', 'group-profile-info', '_bx_groups_page_title_sys_profile_info', '_bx_groups_page_title_profile_info', 'bx_groups', 5, 2147483647, 1, 'page.php?i=group-profile-info', '', '', '', 0, 1, 0, 'BxGroupsPageEntry', 'modules/boonex/groups/classes/BxGroupsPageEntry.php');
 
@@ -94,8 +101,14 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 ('bx_groups_profile_info', 1, 'bx_groups', '_bx_groups_page_block_title_system_profile_info', '_bx_groups_page_block_title_profile_info_link', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_groups\";s:6:\"method\";s:16:\"entity_info_full\";}', 0, 0, 1, 1),
 ('bx_groups_profile_info', 1, 'bx_groups', '', '_bx_groups_page_block_title_profile_description', 13, 2147483647, 'service', 'a:2:{s:6:\"module\";s:9:\"bx_groups\";s:6:\"method\";s:17:\"entity_text_block\";}', 0, 0, 1, 2);
 
--- PAGE: group fans
+-- PAGE: manage profile pricing
+INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
+('bx_groups_profile_pricing', 'edit-group-pricing', '_bx_groups_page_title_sys_profile_pricing', '_bx_groups_page_title_profile_pricing', 'bx_groups', 5, 2147483647, 1, 'page.php?i=edit-group-pricing', '', '', '', 0, 1, 0, 'BxGroupsPageEntry', 'modules/boonex/groups/classes/BxGroupsPageEntry.php');
 
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES 
+('bx_groups_profile_pricing', 1, 'bx_groups', '_bx_groups_page_block_title_system_profile_pricing', '_bx_groups_page_block_title_profile_pricing_link', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:14:"entity_pricing";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 0, 0, 1, 1);
+
+-- PAGE: group fans
 INSERT INTO `sys_objects_page`(`object`, `uri`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
 ('bx_groups_fans', 'group-fans', '_bx_groups_page_title_sys_group_fans', '_bx_groups_page_title_group_fans', 'bx_groups', 5, 2147483647, 1, 'page.php?i=group-fans', '', '', '', 0, 1, 0, 'BxGroupsPageEntry', 'modules/boonex/groups/classes/BxGroupsPageEntry.php');
 
@@ -213,7 +226,8 @@ INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
 ('bx_groups_view_actions', 'bx_groups', '_bx_groups_menu_set_title_view_profile_actions', 0);
 
 INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `editable`, `order`) VALUES 
-('bx_groups_view_actions', 'bx_groups', 'profile-fan-add', '_bx_groups_menu_item_title_system_become_fan', '{title_add_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_groups_fans\', \'add\', \'{profile_id}\')', '', 'sign-in-alt', '', 0, 2147483647, '', 1, 0, 0, 5),
+('bx_groups_view_actions', 'bx_groups', 'join-group-profile', '_bx_groups_menu_item_title_system_pay_and_join', '_bx_groups_menu_item_title_pay_and_join', 'page.php?i=join-group-profile&profile_id={profile_id}', '', '', 'sign-in-alt', '', 0, 2147483647, 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:22:"is_paid_join_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 0, 5),
+('bx_groups_view_actions', 'bx_groups', 'profile-fan-add', '_bx_groups_menu_item_title_system_become_fan', '{title_add_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_groups_fans\', \'add\', \'{profile_id}\')', '', 'sign-in-alt', '', 0, 2147483647, 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:22:"is_free_join_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 0, 10),
 ('bx_groups_view_actions', 'bx_groups', 'profile-subscribe-add', '_bx_groups_menu_item_title_system_subscribe', '_bx_groups_menu_item_title_subscribe', 'javascript:void(0)', 'bx_conn_action(this, \'sys_profiles_subscriptions\', \'add\', \'{profile_id}\')', '', 'check', '', 0, 2147483647, '', 1, 0, 1, 20),
 ('bx_groups_view_actions', 'bx_groups', 'profile-set-badges', '_sys_menu_item_title_system_set_badges', '_sys_menu_item_title_set_badges', 'javascript:void(0)', 'bx_menu_popup(''sys_set_badges'', window, {}, {module: ''bx_groups'', content_id: {content_id}});', '', 'check-circle', '', 0, 192, 'a:2:{s:6:"module";s:9:"bx_groups";s:6:"method";s:19:"is_badges_avaliable";}', 1, 0, 1, 30),
 ('bx_groups_view_actions', 'bx_groups', 'profile-actions-more', '_bx_groups_menu_item_title_system_more_actions', '_bx_groups_menu_item_title_more_actions', 'javascript:void(0)', 'bx_menu_popup(''bx_groups_view_actions_more'', this, {}, {profile_id:{profile_id}});', '', 'cog', 'bx_groups_view_actions_more', 1, 2147483647, '', 1, 0, 1, 9999);
@@ -226,13 +240,14 @@ INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `templat
 INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
 ('bx_groups_view_actions_more', 'bx_groups', '_bx_groups_menu_set_title_view_profile_actions_more', 0);
 
-INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
-('bx_groups_view_actions_more', 'bx_groups', 'profile-fan-remove', '_bx_groups_menu_item_title_system_leave_group', '{title_remove_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_groups_fans\', \'remove\', \'{profile_id}\')', '', 'sign-out-alt', '', 2147483647, 1, 0, 10),
-('bx_groups_view_actions_more', 'bx_groups', 'profile-subscribe-remove', '_bx_groups_menu_item_title_system_unsubscribe', '_bx_groups_menu_item_title_unsubscribe', 'javascript:void(0)', 'bx_conn_action(this, \'sys_profiles_subscriptions\', \'remove\', \'{profile_id}\')', '', 'check', '', 2147483647, 1, 0, 20),
-('bx_groups_view_actions_more', 'bx_groups', 'edit-group-cover', '_bx_groups_menu_item_title_system_edit_cover', '_bx_groups_menu_item_title_edit_cover', 'page.php?i=edit-group-cover&id={content_id}', '', '', 'edit', '', 2147483647, 1, 0, 30),
-('bx_groups_view_actions_more', 'bx_groups', 'edit-group-profile', '_bx_groups_menu_item_title_system_edit_profile', '_bx_groups_menu_item_title_edit_profile', 'page.php?i=edit-group-profile&id={content_id}', '', '', 'pencil-alt', '', 2147483647, 1, 0, 40),
-('bx_groups_view_actions_more', 'bx_groups', 'invite-to-group', '_bx_groups_menu_item_title_system_invite', '_bx_groups_menu_item_title_invite', 'page.php?i=invite-to-group&id={content_id}', '', '', 'user-friends', '', 2147483647, 1, 0, 42),
-('bx_groups_view_actions_more', 'bx_groups', 'delete-group-profile', '_bx_groups_menu_item_title_system_delete_profile', '_bx_groups_menu_item_title_delete_profile', 'page.php?i=delete-group-profile&id={content_id}', '', '', 'remove', '', 2147483647, 1, 0, 50);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_groups_view_actions_more', 'bx_groups', 'profile-fan-remove', '_bx_groups_menu_item_title_system_leave_group', '{title_remove_fan}', 'javascript:void(0)', 'bx_conn_action(this, \'bx_groups_fans\', \'remove\', \'{profile_id}\')', '', 'sign-out-alt', '', 2147483647, '', 1, 0, 10),
+('bx_groups_view_actions_more', 'bx_groups', 'profile-subscribe-remove', '_bx_groups_menu_item_title_system_unsubscribe', '_bx_groups_menu_item_title_unsubscribe', 'javascript:void(0)', 'bx_conn_action(this, \'sys_profiles_subscriptions\', \'remove\', \'{profile_id}\')', '', 'check', '', 2147483647, '', 1, 0, 20),
+('bx_groups_view_actions_more', 'bx_groups', 'edit-group-cover', '_bx_groups_menu_item_title_system_edit_cover', '_bx_groups_menu_item_title_edit_cover', 'page.php?i=edit-group-cover&id={content_id}', '', '', 'edit', '', 2147483647, '', 1, 0, 30),
+('bx_groups_view_actions_more', 'bx_groups', 'edit-group-profile', '_bx_groups_menu_item_title_system_edit_profile', '_bx_groups_menu_item_title_edit_profile', 'page.php?i=edit-group-profile&id={content_id}', '', '', 'pencil-alt', '', 2147483647, '', 1, 0, 40),
+('bx_groups_view_actions_more', 'bx_groups', 'edit-group-pricing', '_bx_groups_menu_item_title_system_edit_pricing', '_bx_groups_menu_item_title_edit_pricing', 'page.php?i=edit-group-pricing&profile_id={profile_id}', '', '', 'money-check-alt', '', 2147483647, 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:20:"is_pricing_avaliable";s:6:"params";a:1:{i:0;s:12:"{profile_id}";}}', 1, 0, 41),
+('bx_groups_view_actions_more', 'bx_groups', 'invite-to-group', '_bx_groups_menu_item_title_system_invite', '_bx_groups_menu_item_title_invite', 'page.php?i=invite-to-group&id={content_id}', '', '', 'user-friends', '', 2147483647, '', 1, 0, 42),
+('bx_groups_view_actions_more', 'bx_groups', 'delete-group-profile', '_bx_groups_menu_item_title_system_delete_profile', '_bx_groups_menu_item_title_delete_profile', 'page.php?i=delete-group-profile&id={content_id}', '', '', 'remove', '', 2147483647, '', 1, 0, 50);
 
 -- MENU: all actions menu for view entry 
 
@@ -243,6 +258,7 @@ INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES
 ('bx_groups_view_actions_all', 'bx_groups', '_sys_menu_set_title_view_actions', 0);
 
 INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `submenu_popup`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `order`) VALUES 
+('bx_groups_view_actions_all', 'bx_groups', 'join-group-profile', '_bx_groups_menu_item_title_system_become_fan_paid', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 5),
 ('bx_groups_view_actions_all', 'bx_groups', 'profile-fan-add', '_bx_groups_menu_item_title_system_become_fan', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 10),
 ('bx_groups_view_actions_all', 'bx_groups', 'profile-fan-remove', '_bx_groups_menu_item_title_system_leave_group', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 20),
 ('bx_groups_view_actions_all', 'bx_groups', 'profile-subscribe-add', '_bx_groups_menu_item_title_system_subscribe', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 30),
@@ -262,6 +278,7 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 ('bx_groups_view_actions_all', 'bx_groups', 'social-sharing-pinterest', '_sys_menu_item_title_system_social_sharing_pinterest', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 330),
 ('bx_groups_view_actions_all', 'bx_groups', 'edit-group-cover', '_bx_groups_menu_item_title_system_edit_cover', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 400),
 ('bx_groups_view_actions_all', 'bx_groups', 'edit-group-profile', '_bx_groups_menu_item_title_system_edit_profile', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 410),
+('bx_groups_view_actions_all', 'bx_groups', 'edit-group-pricing', '_bx_groups_menu_item_title_system_profile_pricing', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 415),
 ('bx_groups_view_actions_all', 'bx_groups', 'invite-to-group', '_bx_groups_menu_item_title_system_invite', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 420),
 ('bx_groups_view_actions_all', 'bx_groups', 'delete-group-profile', '_bx_groups_menu_item_title_system_delete_profile', '', '', '', '', '', '', '', 0, 2147483647, '', 1, 0, 430),
 ('bx_groups_view_actions_all', 'bx_groups', 'more-auto', '_sys_menu_item_title_system_va_more_auto', '_sys_menu_item_title_va_more_auto', 'javascript:void(0)', '', '', 'ellipsis-v', '', '', 0, 2147483647, '', 1, 0, 9999);
@@ -315,19 +332,20 @@ INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `templat
 INSERT INTO `sys_menu_sets`(`set_name`, `module`, `title`, `deletable`) VALUES 
 ('bx_groups_snippet_meta', 'bx_groups', '_sys_menu_set_title_snippet_meta', 0);
 
-INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `editable`, `order`) VALUES
-('bx_groups_snippet_meta', 'bx_groups', 'privacy', '_bx_groups_menu_item_title_system_sm_privacy', '_bx_groups_menu_item_title_sm_privacy', '', '', '', '', '', 2147483647, 1, 0, 1, 1),
-('bx_groups_snippet_meta', 'bx_groups', 'date', '_sys_menu_item_title_system_sm_date', '_sys_menu_item_title_sm_date', '', '', '', '', '', 2147483647, 0, 0, 1, 2),
-('bx_groups_snippet_meta', 'bx_groups', 'tags', '_sys_menu_item_title_system_sm_tags', '_sys_menu_item_title_sm_tags', '', '', '', '', '', 2147483647, 0, 0, 1, 3),
-('bx_groups_snippet_meta', 'bx_groups', 'views', '_sys_menu_item_title_system_sm_views', '_sys_menu_item_title_sm_views', '', '', '', '', '', 2147483647, 0, 0, 1, 4),
-('bx_groups_snippet_meta', 'bx_groups', 'comments', '_sys_menu_item_title_system_sm_comments', '_sys_menu_item_title_sm_comments', '', '', '', '', '', 2147483647, 0, 0, 1, 5),
-('bx_groups_snippet_meta', 'bx_groups', 'members', '_sys_menu_item_title_system_sm_members', '_sys_menu_item_title_sm_members', '', '', '', '', '', 2147483647, 1, 0, 1, 6),
-('bx_groups_snippet_meta', 'bx_groups', 'subscribers', '_sys_menu_item_title_system_sm_subscribers', '_sys_menu_item_title_sm_subscribers', '', '', '', '', '', 2147483647, 0, 0, 1, 7),
-('bx_groups_snippet_meta', 'bx_groups', 'nl', '_sys_menu_item_title_system_sm_nl', '_sys_menu_item_title_sm_nl', '', '', '', '', '', 2147483647, 1, 0, 1, 8),
-('bx_groups_snippet_meta', 'bx_groups', 'join', '_sys_menu_item_title_system_sm_join', '_sys_menu_item_title_sm_join', '', '', '', '', '', 2147483647, 1, 0, 1, 9),
-('bx_groups_snippet_meta', 'bx_groups', 'leave', '_sys_menu_item_title_system_sm_leave', '_sys_menu_item_title_sm_leave', '', '', '', '', '', 2147483647, 0, 0, 1, 10),
-('bx_groups_snippet_meta', 'bx_groups', 'subscribe', '_sys_menu_item_title_system_sm_subscribe', '_sys_menu_item_title_sm_subscribe', '', '', '', '', '', 2147483647, 0, 0, 1, 11),
-('bx_groups_snippet_meta', 'bx_groups', 'unsubscribe', '_sys_menu_item_title_system_sm_unsubscribe', '_sys_menu_item_title_sm_unsubscribe', '', '', '', '', '', 2147483647, 0, 0, 1, 12);
+INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `visibility_custom`, `active`, `copyable`, `editable`, `order`) VALUES
+('bx_groups_snippet_meta', 'bx_groups', 'privacy', '_bx_groups_menu_item_title_system_sm_privacy', '_bx_groups_menu_item_title_sm_privacy', '', '', '', '', '', 2147483647, '', 1, 0, 1, 1),
+('bx_groups_snippet_meta', 'bx_groups', 'date', '_sys_menu_item_title_system_sm_date', '_sys_menu_item_title_sm_date', '', '', '', '', '', 2147483647, '', 0, 0, 1, 2),
+('bx_groups_snippet_meta', 'bx_groups', 'tags', '_sys_menu_item_title_system_sm_tags', '_sys_menu_item_title_sm_tags', '', '', '', '', '', 2147483647, '', 0, 0, 1, 3),
+('bx_groups_snippet_meta', 'bx_groups', 'views', '_sys_menu_item_title_system_sm_views', '_sys_menu_item_title_sm_views', '', '', '', '', '', 2147483647, '', 0, 0, 1, 4),
+('bx_groups_snippet_meta', 'bx_groups', 'comments', '_sys_menu_item_title_system_sm_comments', '_sys_menu_item_title_sm_comments', '', '', '', '', '', 2147483647, '', 0, 0, 1, 5),
+('bx_groups_snippet_meta', 'bx_groups', 'members', '_sys_menu_item_title_system_sm_members', '_sys_menu_item_title_sm_members', '', '', '', '', '', 2147483647, '', 1, 0, 1, 6),
+('bx_groups_snippet_meta', 'bx_groups', 'subscribers', '_sys_menu_item_title_system_sm_subscribers', '_sys_menu_item_title_sm_subscribers', '', '', '', '', '', 2147483647, '', 0, 0, 1, 7),
+('bx_groups_snippet_meta', 'bx_groups', 'nl', '_sys_menu_item_title_system_sm_nl', '_sys_menu_item_title_sm_nl', '', '', '', '', '', 2147483647, '', 1, 0, 1, 8),
+('bx_groups_snippet_meta', 'bx_groups', 'join-paid', '_sys_menu_item_title_system_sm_join_paid', '_sys_menu_item_title_sm_join_paid', '', '', '', '', '', 2147483647, 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:33:"is_paid_join_avaliable_by_content";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 1, 9),
+('bx_groups_snippet_meta', 'bx_groups', 'join', '_sys_menu_item_title_system_sm_join', '_sys_menu_item_title_sm_join', '', '', '', '', '', 2147483647, 'a:3:{s:6:"module";s:9:"bx_groups";s:6:"method";s:33:"is_free_join_avaliable_by_content";s:6:"params";a:1:{i:0;s:12:"{content_id}";}}', 1, 0, 1, 10),
+('bx_groups_snippet_meta', 'bx_groups', 'leave', '_sys_menu_item_title_system_sm_leave', '_sys_menu_item_title_sm_leave', '', '', '', '', '', 2147483647, '', 0, 0, 1, 12),
+('bx_groups_snippet_meta', 'bx_groups', 'subscribe', '_sys_menu_item_title_system_sm_subscribe', '_sys_menu_item_title_sm_subscribe', '', '', '', '', '', 2147483647, '', 0, 0, 1, 13),
+('bx_groups_snippet_meta', 'bx_groups', 'unsubscribe', '_sys_menu_item_title_system_sm_unsubscribe', '_sys_menu_item_title_sm_unsubscribe', '', '', '', '', '', 2147483647, '', 0, 0, 1, 14);
 
 -- MENU: profile stats
 SET @iNotifMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name` = 'sys_profile_stats' AND `active` = 1 LIMIT 1);
@@ -460,7 +478,9 @@ INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `fie
 INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `params`, `order`) VALUES
 ('bx_groups_fans', 'name', '_sys_name', '50%', '', 10),
 ('bx_groups_fans', 'role', '_bx_groups_txt_role', '10%', '', 15),
-('bx_groups_fans', 'actions', '', '40%', '', 20);
+('bx_groups_fans', 'role_added', '_bx_groups_txt_role_added', '10%', '', 16),
+('bx_groups_fans', 'role_expired', '_bx_groups_txt_role_expired', '10%', '', 17),
+('bx_groups_fans', 'actions', '', '20%', '', 20);
 
 INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `confirm`, `order`) VALUES
 ('bx_groups_fans', 'single', 'accept', '_sys_accept', '', 0, 10),
@@ -511,6 +531,34 @@ INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon
 ('bx_groups_common', 'bulk', 'delete_with_content', '_bx_groups_grid_action_title_adm_delete_with_content', '', 0, 1, 2),
 ('bx_groups_common', 'single', 'settings', '_bx_groups_grid_action_title_adm_more_actions', 'cog', 1, 0, 1);
 
+-- GRIDS: Pricing
+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_groups_prices_manage', 'Sql', 'SELECT * FROM `bx_groups_prices` WHERE 1 ', 'bx_groups_prices', 'id', 'order', '', '', 100, NULL, 'start', '', 'period,period_unit,price', '', 'like', '', '', 2147483647, 'BxGroupsGridPricesManage', 'modules/boonex/groups/classes/BxGroupsGridPricesManage.php'),
+('bx_groups_prices_view', 'Sql', 'SELECT * FROM `bx_groups_prices` WHERE 1 ', 'bx_groups_prices', 'id', 'order', '', '', 100, NULL, 'start', '', 'period,period_unit,price', '', 'like', '', '', 2147483647, 'BxGroupsGridPricesView', 'modules/boonex/groups/classes/BxGroupsGridPricesView.php');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
+('bx_groups_prices_manage', 'checkbox', '_sys_select', '1%', 0, '', '', 1),
+('bx_groups_prices_manage', 'order', '', '1%', 0, '', '', 2),
+('bx_groups_prices_manage', 'name', '_bx_groups_grid_column_title_name', '38%', 0, 32, '', 3),
+('bx_groups_prices_manage', 'price', '_bx_groups_grid_column_title_price', '20%', 0, 16, '', 4),
+('bx_groups_prices_manage', 'period', '_bx_groups_grid_column_title_period', '20%', 0, 16, '', 5),
+('bx_groups_prices_manage', 'actions', '', '20%', 0, '', '', 6),
+
+('bx_groups_prices_view', 'role_id', '_bx_groups_grid_column_title_role_id', '40%', 0, 32, '', 1),
+('bx_groups_prices_view', 'price', '_bx_groups_grid_column_title_price', '20%', 0, 16, '', 2),
+('bx_groups_prices_view', 'period', '_bx_groups_grid_column_title_period', '20%', 0, 16, '', 3),
+('bx_groups_prices_view', 'actions', '', '20%', 0, '', '', 4);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('bx_groups_prices_manage', 'independent', 'add', '_bx_groups_grid_action_title_add', '', 0, 0, 1),
+('bx_groups_prices_manage', 'single', 'edit', '_bx_groups_grid_action_title_edit', 'pencil-alt', 1, 0, 1),
+('bx_groups_prices_manage', 'single', 'delete', '_bx_groups_grid_action_title_delete', 'remove', 1, 1, 2),
+('bx_groups_prices_manage', 'bulk', 'delete', '_bx_groups_grid_action_title_delete', '', 0, 1, 1),
+
+('bx_groups_prices_view', 'single', 'buy', '_bx_groups_grid_action_title_buy', 'cart-plus', 0, 0, 1),
+('bx_groups_prices_view', 'single', 'subscribe', '_bx_groups_grid_action_title_subscribe', 'credit-card', 0, 0, 2),
+('bx_groups_prices_view', 'single', 'choose', '_bx_groups_grid_action_title_choose', 'far check-square', 0, 0, 3);
+
 
 -- ALERTS
 
@@ -565,3 +613,7 @@ INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `
 ('bx_groups_cover_crop', 1, 'BxGroupsUploaderCoverCrop', 'modules/boonex/groups/classes/BxGroupsUploaderCoverCrop.php'),
 ('bx_groups_picture_crop', 1, 'BxGroupsUploaderPictureCrop', 'modules/boonex/groups/classes/BxGroupsUploaderPictureCrop.php');
 
+
+-- CRON
+INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
+('bx_groups_pruning', '0 0 * * *', 'BxGroupsCronPruning', 'modules/boonex/groups/classes/BxGroupsCronPruning.php', '');

+ 64 - 0
modules/boonex/groups/install/sql/install.sql

@@ -194,6 +194,9 @@ CREATE TABLE IF NOT EXISTS `bx_groups_admins` (
   `group_profile_id` int(10) unsigned NOT NULL,
   `fan_id` int(10) unsigned NOT NULL,
   `role` int(10) unsigned NOT NULL default '0',
+  `order` varchar(32) NOT NULL default '',
+  `added` int(11) unsigned NOT NULL default '0',
+  `expired` int(11) unsigned NOT NULL default '0',
   PRIMARY KEY (`id`),
   UNIQUE KEY `admin` (`group_profile_id`,`fan_id`)
 );
@@ -235,6 +238,20 @@ CREATE TABLE IF NOT EXISTS `bx_groups_invites` (
   PRIMARY KEY (`id`)
 );
 
+-- TABLE: Pricing
+CREATE TABLE IF NOT EXISTS `bx_groups_prices` (
+  `id` int(11) NOT NULL auto_increment,
+  `profile_id` int(11) NOT NULL default '0',
+  `role_id` int(11) unsigned NOT NULL default '0',
+  `name` varchar(128) NOT NULL default '',
+  `period` int(11) unsigned NOT NULL default '1',
+  `period_unit` varchar(32) NOT NULL default '',
+  `price` float unsigned NOT NULL default '1',
+  `order` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`),
+  UNIQUE KEY `type` (`profile_id`, `role_id`,`period`, `period_unit`)
+);
 
 -- STORAGES & TRANSCODERS
 INSERT INTO `sys_objects_storage` (`object`, `engine`, `params`, `token_life`, `cache_control`, `levels`, `table_files`, `ext_mode`, `ext_allow`, `ext_deny`, `quota_size`, `current_size`, `quota_number`, `current_number`, `max_file_size`, `ts`) VALUES
@@ -325,6 +342,43 @@ INSERT INTO `sys_form_display_inputs`(`display_name`, `input_name`, `visible_for
 ('bx_group_view_full', 'group_cat', 2147483647, 1, 2),
 ('bx_group_view_full', 'group_desc', 2147483647, 1, 3);
 
+-- FORMS: Price
+INSERT INTO `sys_objects_form` (`object`, `module`, `title`, `action`, `form_attrs`, `submit_name`, `table`, `key`, `uri`, `uri_title`, `params`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_groups_price', 'bx_groups', '_bx_groups_form_price', '', '', 'do_submit', 'bx_groups_prices', 'id', '', '', '', 0, 1, '', '');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('bx_groups_price_add', 'bx_groups', 'bx_groups_price', '_bx_groups_form_price_display_add', 0),
+('bx_groups_price_edit', 'bx_groups', 'bx_groups_price', '_bx_groups_form_price_display_edit', 0);
+
+INSERT INTO `sys_form_inputs` (`object`, `module`, `name`, `value`, `values`, `checked`, `type`, `caption_system`, `caption`, `info`, `required`, `collapsed`, `html`, `attrs`, `attrs_tr`, `attrs_wrapper`, `checker_func`, `checker_params`, `checker_error`, `db_pass`, `db_params`, `editable`, `deletable`) VALUES
+('bx_groups_price', 'bx_groups', 'id', '', '', 0, 'hidden', '_bx_groups_form_price_input_sys_id', '', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'role_id', '', '', 0, 'hidden', '_bx_groups_form_price_input_sys_role_id', '', '', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'period', '', '', 0, 'text', '_bx_groups_form_price_input_sys_period', '_bx_groups_form_price_input_period', '_bx_groups_form_price_input_inf_period', 1, 0, 0, '', '', '', '', '', '', 'Int', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'period_unit', '', '#!bx_groups_period_units', 0, 'select', '_bx_groups_form_price_input_sys_period_unit', '_bx_groups_form_price_input_period_unit', '_bx_groups_form_price_input_inf_period_unit', 1, 0, 0, '', '', '', '', '', '', 'Xss', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'price', '', '', 0, 'text', '_bx_groups_form_price_input_sys_price', '_bx_groups_form_price_input_price', '_bx_groups_form_price_input_inf_price', 1, 0, 0, '', '', '', '', '', '', 'Float', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'controls', '', 'do_submit,do_cancel', 0, 'input_set', '', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'do_submit', '_bx_groups_form_price_input_do_submit', '', 0, 'submit', '_bx_groups_form_price_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 1, 0),
+('bx_groups_price', 'bx_groups', 'do_cancel', '_bx_groups_form_price_input_do_cancel', '', 0, 'button', '_bx_groups_form_price_input_sys_do_cancel', '', '', 0, 0, 0, 'a:2:{s:7:"onclick";s:45:"$(''.bx-popup-applied:visible'').dolPopupHide()";s:5:"class";s:22:"bx-def-margin-sec-left";}', '', '', '', '', '', '', '', 1, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('bx_groups_price_add', 'id', 2147483647, 0, 1),
+('bx_groups_price_add', 'role_id', 2147483647, 1, 2),
+('bx_groups_price_add', 'price', 2147483647, 1, 3),
+('bx_groups_price_add', 'period', 2147483647, 1, 4),
+('bx_groups_price_add', 'period_unit', 2147483647, 1, 5),
+('bx_groups_price_add', 'controls', 2147483647, 1, 6),
+('bx_groups_price_add', 'do_submit', 2147483647, 1, 7),
+('bx_groups_price_add', 'do_cancel', 2147483647, 1, 8),
+
+('bx_groups_price_edit', 'id', 2147483647, 1, 1),
+('bx_groups_price_edit', 'role_id', 2147483647, 1, 2),
+('bx_groups_price_edit', 'price', 2147483647, 1, 3),
+('bx_groups_price_edit', 'period', 2147483647, 1, 4),
+('bx_groups_price_edit', 'period_unit', 2147483647, 1, 5),
+('bx_groups_price_edit', 'controls', 2147483647, 1, 6),
+('bx_groups_price_edit', 'do_submit', 2147483647, 1, 7),
+('bx_groups_price_edit', 'do_cancel', 2147483647, 1, 8);
+
 -- PRE-VALUES
 INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
 ('bx_groups_cats', '_bx_groups_pre_lists_cats', 'bx_groups', '0');
@@ -346,6 +400,16 @@ INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALU
 ('bx_groups_roles', '1', 2, '_bx_groups_role_administrator', ''),
 ('bx_groups_roles', '2', 3, '_bx_groups_role_moderator', '');
 
+INSERT INTO `sys_form_pre_lists`(`key`, `title`, `module`, `use_for_sets`) VALUES
+('bx_groups_period_units', '_bx_groups_pre_lists_period_units', 'bx_groups', '0');
+
+INSERT INTO `sys_form_pre_values`(`Key`, `Value`, `Order`, `LKey`, `LKey2`) VALUES
+('bx_groups_period_units', '', 0, '_sys_please_select', ''),
+('bx_groups_period_units', 'day', 1, '_bx_groups_period_unit_day', ''),
+('bx_groups_period_units', 'week', 2, '_bx_groups_period_unit_week', ''),
+('bx_groups_period_units', 'month', 3, '_bx_groups_period_unit_month', ''),
+('bx_groups_period_units', 'year', 4, '_bx_groups_period_unit_year', '');
+
 -- COMMENTS
 INSERT INTO `sys_objects_cmts` (`Name`, `Module`, `Table`, `CharsPostMin`, `CharsPostMax`, `CharsDisplayMax`, `Html`, `PerView`, `PerViewReplies`, `BrowseType`, `IsBrowseSwitch`, `PostFormPosition`, `NumberOfLevels`, `IsDisplaySwitch`, `IsRatable`, `ViewingThreshold`, `IsOn`, `RootStylePrefix`, `BaseUrl`, `ObjectVote`, `TriggerTable`, `TriggerFieldId`, `TriggerFieldAuthor`, `TriggerFieldTitle`, `TriggerFieldComments`, `ClassName`, `ClassFile`) VALUES
 ('bx_groups', 'bx_groups', 'bx_groups_cmts', 1, 5000, 1000, 3, 5, 3, 'tail', 1, 'bottom', 1, 1, 1, -3, 1, 'cmt', 'page.php?i=view-group-profile&id={object_id}', '', 'bx_groups_data', 'id', 'author', 'group_name', 'comments', '', ''),

+ 2 - 2
modules/boonex/groups/install/sql/uninstall.sql

@@ -1,7 +1,7 @@
 
 -- TABLES
 
-DROP TABLE IF EXISTS `bx_groups_data`, `bx_groups_pics`, `bx_groups_pics_resized`, `bx_groups_cmts`, `bx_groups_cmts_notes`, `bx_groups_views_track`, `bx_groups_meta_keywords`, `bx_groups_meta_locations`, `bx_groups_meta_mentions`, `bx_groups_fans`, `bx_groups_admins`, `bx_groups_votes`, `bx_groups_votes_track`, `bx_groups_reports`, `bx_groups_reports_track`, `bx_groups_favorites_track`, `bx_groups_scores`, `bx_groups_scores_track`, `bx_groups_invites`;
+DROP TABLE IF EXISTS `bx_groups_data`, `bx_groups_pics`, `bx_groups_pics_resized`, `bx_groups_cmts`, `bx_groups_cmts_notes`, `bx_groups_views_track`, `bx_groups_meta_keywords`, `bx_groups_meta_locations`, `bx_groups_meta_mentions`, `bx_groups_fans`, `bx_groups_admins`, `bx_groups_votes`, `bx_groups_votes_track`, `bx_groups_reports`, `bx_groups_reports_track`, `bx_groups_favorites_track`, `bx_groups_scores`, `bx_groups_scores_track`, `bx_groups_invites`, `bx_groups_prices`;
 
 -- PROFILES
 
@@ -21,7 +21,7 @@ DELETE FROM `sys_transcoder_images_files` WHERE `transcoder_object` IN('bx_group
 DELETE FROM `sys_objects_form` WHERE `module` = 'bx_groups';
 DELETE FROM `sys_form_displays` WHERE `module` = 'bx_groups';
 DELETE FROM `sys_form_inputs` WHERE `module` = 'bx_groups';
-DELETE FROM `sys_form_display_inputs` WHERE `display_name` IN('bx_group_add', 'bx_group_delete', 'bx_group_edit', 'bx_group_edit_cover', 'bx_group_view', 'bx_group_view_full', 'bx_group_invite');
+DELETE FROM `sys_form_display_inputs` WHERE `display_name` IN('bx_group_add', 'bx_group_delete', 'bx_group_edit', 'bx_group_edit_cover', 'bx_group_view', 'bx_group_view_full', 'bx_group_invite', 'bx_groups_price_add', 'bx_groups_price_edit');
 
 -- PRE-VALUES
 

+ 33 - 0
modules/boonex/groups/js/prices.js

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Groups Groups
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+
+function BxGroupsPrices(oOptions) {
+    this._sActionsUri = oOptions.sActionUri;
+    this._sActionsUrl = oOptions.sActionUrl;
+    this._sObjNameGrid = oOptions.sObjNameGrid;
+    this._sObjName = oOptions.sObjName == undefined ? 'oBxGroupsPrices' : oOptions.sObjName;
+    this._aHtmlIds = oOptions.aHtmlIds == undefined ? {} : oOptions.aHtmlIds;
+    this._oRequestParams = oOptions.oRequestParams == undefined ? {} : oOptions.oRequestParams;
+}
+
+BxGroupsPrices.prototype.onChangeRole = function() {
+	this.reloadGrid($('#bx-grid-level-' + this._sObjNameGrid).val());
+};
+
+BxGroupsPrices.prototype.reloadGrid = function(iRoleId) {
+    if(glGrids[this._sObjNameGrid]._oQueryAppend['role_id'] == iRoleId)
+        return;
+
+    glGrids[this._sObjNameGrid]._oQueryAppend['role_id'] = iRoleId;
+    glGrids[this._sObjNameGrid].reload(0);
+};
+
+/** @} */

+ 17 - 0
modules/boonex/groups/template/css/prices.css

@@ -0,0 +1,17 @@
+/*--- Top Controls ---*/
+div.bx-grid-header div.bx-form-element-wrapper {
+    position: relative;
+    float: left;
+}
+
+div.bx-grid-header div#bx-form-element-role {
+    margin-right: 20px;
+}
+div.bx-grid-header div#bx-form-element-role,
+div.bx-grid-header div#bx-form-element-keyword {
+    margin-top: 0px;
+}
+
+/*--- Add Price Popup ---*/
+div.bx-groups-pricing-form {}
+div.bx-groups-pricing-form-cnt {}

+ 1 - 0
modules/boonex/groups/template/popup_price.html

@@ -0,0 +1 @@
+<bx_include_base:../modules/base/groups/template/popup_price.html />

+ 8 - 3
modules/boonex/organizations/classes/BxOrgsConfig.php

@@ -9,7 +9,7 @@
  * @{
  */
 
-class BxOrgsConfig extends BxBaseModProfileConfig
+class BxOrgsConfig extends BxBaseModGroupsConfig
 {
     function __construct($aModule)
     {
@@ -26,8 +26,12 @@ class BxOrgsConfig extends BxBaseModProfileConfig
             'edit-organization-cover' => 'checkAllowedChangeCover',
             'invite-to-organization' => 'checkAllowedInvite',
             'delete-organization-profile' => 'checkAllowedDelete',
-            'profile-fan-add' => 'checkAllowedFanAdd',
-            'profile-fan-remove' => 'checkAllowedFanRemove',
+            'profile-friend-add' => 'checkAllowedFriendAdd',
+            'profile-friend-remove' => 'checkAllowedFriendRemove',
+            'profile-relation-add' => 'checkAllowedRelationAdd',
+            'profile-relation-remove' => 'checkAllowedRelationRemove',
+            'profile-set-acl-level' => 'checkAllowedSetMembership',
+            'messenger' => 'checkAllowedCompose',
         ));
 
         $this->CNF = array_merge($this->CNF, array (
@@ -150,6 +154,7 @@ class BxOrgsConfig extends BxBaseModProfileConfig
             'OBJECT_PRE_LIST_ROLES' => 'bx_organizations_roles',
             
             'BADGES_AVALIABLE' => true,
+            'ALLOW_AS_CONTEXT' => false,
 
             'EMAIL_FRIEND_REQUEST' => 'bx_organizations_friend_request',
             'EMAIL_INVITATION' => 'bx_organizations_invitation',

+ 15 - 3
modules/boonex/organizations/classes/BxOrgsModule.php

@@ -45,7 +45,10 @@ class BxOrgsModule extends BxBaseModGroupsModule
      */ 
     public function serviceGetSpaceTitle()
     {
-        return BxBaseModProfileModule::serviceGetSpaceTitle();
+        if (isset($this->_oConfig->CNF['ALLOW_AS_CONTEXT']) && $this->_oConfig->CNF['ALLOW_AS_CONTEXT'] == true)
+            return _t($this->_oConfig->CNF['T']['txt_sample_single']);
+        else
+            return BxBaseModProfileModule::serviceGetSpaceTitle();
     }
     
     /**
@@ -53,8 +56,17 @@ class BxOrgsModule extends BxBaseModGroupsModule
      */ 
     public function serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects = false)
     {
-        if (false === $aConnectionObjects)
-            $aConnectionObjects = array('sys_profiles_friends');
+        if (false === $aConnectionObjects){
+            if (isset($this->_oConfig->CNF['ALLOW_AS_CONTEXT']) && $this->_oConfig->CNF['ALLOW_AS_CONTEXT'] == true){
+                $aConnectionObjects = array('sys_profiles_subscriptions');
+                if (isset($this->_oConfig->CNF['OBJECT_CONNECTIONS'])){
+                    $aConnectionObjects = array($this->_oConfig->CNF['OBJECT_CONNECTIONS'], 'sys_profiles_subscriptions');
+                }
+            }
+            else{
+                $aConnectionObjects = array('sys_profiles_friends');
+            }
+        } 
         return BxBaseModProfileModule::serviceGetParticipatingProfiles($iProfileId, $aConnectionObjects);
     }
     

+ 3 - 1
modules/boonex/posts/install/sql/enable.sql

@@ -167,8 +167,10 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title`, `designbo
 
 -- PAGES: add page block to profiles modules (trigger* page objects are processed separately upon modules enable/disable)
 SET @iPBCellProfile = 3;
+SET @iPBCellGroup = 4;
 INSERT INTO `sys_pages_blocks` (`object`, `cell_id`, `module`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES
-('trigger_page_profile_view_entry', @iPBCellProfile, 'bx_posts', '_bx_posts_page_block_title_my_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_posts";s:6:"method";s:13:"browse_author";s:6:"params";a:2:{i:0;s:12:"{profile_id}";i:1;a:2:{s:8:"per_page";s:25:"bx_posts_per_page_profile";s:13:"empty_message";b:0;}}}', 0, 0, 0);
+('trigger_page_profile_view_entry', @iPBCellProfile, 'bx_posts', '_bx_posts_page_block_title_my_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_posts";s:6:"method";s:13:"browse_author";s:6:"params";a:2:{i:0;s:12:"{profile_id}";i:1;a:2:{s:8:"per_page";s:25:"bx_posts_per_page_profile";s:13:"empty_message";b:0;}}}', 0, 0, 0),
+('trigger_page_group_view_entry', @iPBCellGroup, 'bx_classes', '_bx_files_page_block_title_group_entries', 11, 2147483647, 'service', 'a:3:{s:6:"module";s:8:"bx_files";s:6:"method";s:19:"browse_group_author";s:6:"params";a:2:{i:0;s:12:"{profile_id}";i:1;a:2:{s:8:"per_page";s:25:"bx_files_per_page_profile";s:13:"empty_message";b:0;}}}', 0, 0, 0);
 
 -- PAGE: service blocks
 

+ 66 - 2
modules/boonex/russian/data/langs/bx_groups/ru.xml

@@ -8,6 +8,7 @@
 	<string name="_bx_groups_option_per_page_browse"><![CDATA[Количество элементов на странице]]></string>
     <string name="_bx_groups_option_num_rss"><![CDATA[Количество элементов в ленте RSS]]></string>
     <string name="_bx_groups_option_searchable_fields"><![CDATA[Поля для поиска]]></string>
+    <string name="_bx_groups_option_enable_paid_join"><![CDATA[Активировать 'Платное присоединение']]></string>
 
 	<string name="_bx_groups_form_profile"><![CDATA[Группы]]></string>
 	<string name="_bx_groups_form_profile_display_add"><![CDATA[Добавление группы]]></string>
@@ -46,6 +47,26 @@
 	<string name="_bx_groups_form_profile_input_delete_confirm_info"><![CDATA[Это действие не может быть отменено.]]></string>
 	<string name="_bx_groups_form_profile_input_delete_confirm_error"><![CDATA[Пожалуйста, отметьте этот флажок, если действительно хотите удалить группу.]]></string>
 
+    <string name="_bx_groups_form_price"><![CDATA[Цена]]></string>
+    <string name="_bx_groups_form_price_display_add"><![CDATA[Добавление]]></string>
+    <string name="_bx_groups_form_price_display_edit"><![CDATA[Редактирование]]></string>
+    <string name="_bx_groups_form_price_input_sys_id"><![CDATA[ID]]></string>
+    <string name="_bx_groups_form_price_input_sys_role_id"><![CDATA[ID роли]]></string>
+    <string name="_bx_groups_form_price_input_sys_period"><![CDATA[Период]]></string>
+    <string name="_bx_groups_form_price_input_period"><![CDATA[Период]]></string>
+    <string name="_bx_groups_form_price_input_inf_period"><![CDATA[Используйте 0 для пожизненного]]></string>
+    <string name="_bx_groups_form_price_input_sys_period_unit"><![CDATA[Единица измерения периода]]></string>
+    <string name="_bx_groups_form_price_input_period_unit"><![CDATA[Единица измерения периода]]></string>
+    <string name="_bx_groups_form_price_input_inf_period_unit"><![CDATA[Оставьте пустым для пожизненного уровня.]]></string>
+    <string name="_bx_groups_form_price_input_err_period_unit"><![CDATA[Здесь Вам необходимо выбрать единицу измерения периода.]]></string>
+    <string name="_bx_groups_form_price_input_sys_price"><![CDATA[Цена]]></string>
+    <string name="_bx_groups_form_price_input_price"><![CDATA[Цена]]></string>
+    <string name="_bx_groups_form_price_input_inf_price"><![CDATA[Введите 0 или оставьте пустым для создания бесплатного уровня.]]></string>
+    <string name="_bx_groups_form_price_input_sys_do_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_groups_form_price_input_do_submit"><![CDATA[Выполнить]]></string>
+    <string name="_bx_groups_form_price_input_sys_do_cancel"><![CDATA[Отменить]]></string>
+    <string name="_bx_groups_form_price_input_do_cancel"><![CDATA[Отменить]]></string>
+
 	<string name="_bx_groups_acl_action_create_profile"><![CDATA[Создать группу]]></string>
 	<string name="_bx_groups_acl_action_delete_profile"><![CDATA[Удалить группу]]></string>
 	<string name="_bx_groups_acl_action_view_profile"><![CDATA[Просмотреть группу]]></string>
@@ -62,6 +83,8 @@
 	<string name="_bx_groups_page_title_edit_profile_cover"><![CDATA[Редактировать обложку]]></string>
 	<string name="_bx_groups_page_title_sys_delete_profile"><![CDATA[Удалить группу]]></string>
 	<string name="_bx_groups_page_title_delete_profile"><![CDATA[Удалить группу]]></string>
+        <string name="_bx_groups_page_title_sys_join_profile"><![CDATA[Присоединиться к группе]]></string>
+	<string name="_bx_groups_page_title_join_profile"><![CDATA[Присоединиться к группе]]></string>
 	<string name="_bx_groups_page_title_sys_view_profile_closed"><![CDATA[Просмотр закрытой группы]]></string>
 	<string name="_bx_groups_page_title_sys_view_profile"><![CDATA[Просмотр группы]]></string>
 	<string name="_bx_groups_page_title_view_profile"><![CDATA[{display_name}]]></string>
@@ -80,6 +103,8 @@
     <string name="_bx_groups_page_title_profile_comments"><![CDATA[Комментарии]]></string>
 	<string name="_bx_groups_page_title_sys_profile_info"><![CDATA[Инфо группы]]></string>
 	<string name="_bx_groups_page_title_profile_info"><![CDATA[Инфо о {display_name}]]></string>
+        <string name="_bx_groups_page_title_sys_profile_pricing"><![CDATA[Цены]]></string>
+	<string name="_bx_groups_page_title_profile_pricing"><![CDATA[Цены]]></string>
 	<string name="_bx_groups_page_title_sys_recent"><![CDATA[Новые группы]]></string>
 	<string name="_bx_groups_page_title_recent"><![CDATA[Новые группы]]></string>
 	<string name="_bx_groups_page_title_sys_manage"><![CDATA[Управление собственными группами]]></string>
@@ -104,9 +129,13 @@
 	<string name="_bx_groups_page_block_title_edit_profile"><![CDATA[Редактировать группу]]></string>
 	<string name="_bx_groups_page_block_title_edit_profile_cover"><![CDATA[Редактировать обложку]]></string>
 	<string name="_bx_groups_page_block_title_delete_profile"><![CDATA[Удалить группу]]></string>
+        <string name="_bx_groups_page_block_title_join_profile"><![CDATA[Присоединиться к группе]]></string>
 	<string name="_bx_groups_page_block_title_profile_info_link"><![CDATA[<a href="{profile_link}">{display_name}</a> - Инфо]]></string>
 	<string name="_bx_groups_page_block_title_profile_info"><![CDATA[Инфо]]></string>
 	<string name="_bx_groups_page_block_title_system_profile_info"><![CDATA[Инфо]]></string>
+        <string name="_bx_groups_page_block_title_system_profile_pricing"><![CDATA[Цены]]></string>
+        <string name="_bx_groups_page_block_title_profile_pricing"><![CDATA[Цены]]></string>
+        <string name="_bx_groups_page_block_title_profile_pricing_link"><![CDATA[Расценки группы: <a href="{profile_link}">{display_name}</a>]]></string>
 	<string name="_bx_groups_page_block_title_profile_description"><![CDATA[Описание]]></string>
 	<string name="_bx_groups_page_block_title_profile_friends_link"><![CDATA[<a href="{profile_link}">{display_name}</a> - Друзья]]></string>
 	<string name="_bx_groups_page_block_title_profile_friends"><![CDATA[Друзья]]></string>
@@ -166,6 +195,8 @@
     <string name="_bx_groups_menu_item_title_delete_with_content"><![CDATA[Удалить вместе с содержимым]]></string>
     <string name="_bx_groups_menu_item_title_system_edit_cover"><![CDATA[Редактировать обложку]]></string>
     <string name="_bx_groups_menu_item_title_edit_cover"><![CDATA[Редактировать обложку]]></string>
+    <string name="_bx_groups_menu_item_title_system_edit_pricing"><![CDATA[Редактировать цены]]></string>
+    <string name="_bx_groups_menu_item_title_edit_pricing"><![CDATA[Редактировать цены]]></string>
     <string name="_bx_groups_menu_item_title_system_edit_profile"><![CDATA[Редактировать группу]]></string>
     <string name="_bx_groups_menu_item_title_edit_profile"><![CDATA[Редактировать группу]]></string>
     <string name="_bx_groups_menu_item_title_system_delete_profile"><![CDATA[Удалить группу]]></string>
@@ -211,6 +242,9 @@
     <string name="_bx_groups_menu_item_title_leave_group_cancel_request"><![CDATA[Отменить присоединение]]></string>
     <string name="_bx_groups_menu_item_title_leave_group_reject_request"><![CDATA[Отвергнуть присоединение]]></string>
 
+    <string name="_bx_groups_menu_item_title_system_pay_and_join"><![CDATA[Оплатить и присоединиться]]></string>
+    <string name="_bx_groups_menu_item_title_pay_and_join"><![CDATA[Оплатить и присоединиться]]></string>
+
     <string name="_bx_groups_menu_item_title_system_subscribe"><![CDATA[Получать уведомления]]></string>
     <string name="_bx_groups_menu_item_title_subscribe"><![CDATA[Получать уведомления]]></string>
     <string name="_bx_groups_menu_item_title_system_unsubscribe"><![CDATA[Отключить уведомления]]></string>
@@ -254,8 +288,24 @@
     <string name="_bx_groups_grid_action_title_adm_audit_context"><![CDATA[Аудит действий в контексте]]></string>
     <string name="_bx_groups_grid_action_title_adm_audit_content"><![CDATA[Аудит действий над контентом]]></string>
   
-	<string name="_bx_groups_grid_action_err_delete"><![CDATA[Невозможно удалить выбранную(ные) группу(ы).]]></string>
-	<string name="_bx_groups_grid_txt_account_manager"><![CDATA[Перенаправить в управление аккаунтом.]]></string>
+    <string name="_bx_groups_grid_action_err_delete"><![CDATA[Невозможно удалить выбранную(ные) группу(ы).]]></string>
+    <string name="_bx_groups_grid_txt_account_manager"><![CDATA[Перенаправить в управление аккаунтом.]]></string>
+
+    <string name="_bx_groups_grid_column_title_name"><![CDATA[Название]]></string>
+    <string name="_bx_groups_grid_column_title_role_id"><![CDATA[Роль]]></string>
+    <string name="_bx_groups_grid_column_title_period"><![CDATA[Период]]></string>
+    <string name="_bx_groups_grid_column_title_price"><![CDATA[Цена]]></string>
+    <string name="_bx_groups_grid_action_title_add"><![CDATA[Добавить]]></string>
+    <string name="_bx_groups_grid_action_title_edit"><![CDATA[Изменить]]></string>
+    <string name="_bx_groups_grid_action_title_delete"><![CDATA[Удалить]]></string>
+    <string name="_bx_groups_grid_action_title_buy"><![CDATA[Купить]]></string>
+    <string name="_bx_groups_grid_action_title_buy_title"><![CDATA[Единовременный платеж]]></string>
+    <string name="_bx_groups_grid_action_title_subscribe"><![CDATA[Арендовать]]></string>
+    <string name="_bx_groups_grid_action_title_subscribe_title"><![CDATA[Подписка на периодические платежи]]></string>
+    <string name="_bx_groups_grid_action_title_choose"><![CDATA[Выбирать]]></string>
+    <string name="_bx_groups_grid_action_title_choose_title"><![CDATA[Бесплатно переключиться на уровень]]></string>
+    <string name="_bx_groups_popup_title_price_add"><![CDATA[Добавить ценовой план]]></string>
+    <string name="_bx_groups_popup_title_price_edit"><![CDATA[Редактировать ценовой план]]></string>
 
     <string name="_bx_groups_chart_growth"><![CDATA[Группы: Развитие]]></string>
     <string name="_bx_groups_chart_growth_speed"><![CDATA[Группы: Скорость развития]]></string>
@@ -267,6 +317,8 @@
     <string name="_bx_groups_txt_account_suspended"><![CDATA[Эта группа заблокирована.]]></string>
     <string name="_bx_groups_txt_N_fans"><![CDATA[{0} участников]]></string>
     <string name="_bx_groups_txt_role"><![CDATA[Роль]]></string>
+    <string name="_bx_groups_txt_role_added"><![CDATA[Начало]]></string>
+    <string name="_bx_groups_txt_role_expired"><![CDATA[Окончание]]></string>
     <string name="_bx_groups_txt_sample_single"><![CDATA[группа]]></string>
     <string name="_bx_groups_txt_sample_single_with_article"><![CDATA[группа]]></string>
     <string name="_bx_groups_txt_sample_comment_single"><![CDATA[комментарий к группе]]></string>
@@ -281,6 +333,12 @@
     <string name="_bx_groups_txt_invite_popup_button_decline"><![CDATA[Отклонить]]></string>
     <string name="_bx_groups_txt_invite_popup_error_invitation_absent"><![CDATA[Извините, произошла ошибка. Вы не можете вступить в эту группу. Приглашение устарело или удалено.]]></string>
     <string name="_bx_groups_txt_invite_popup_error_invitation_wrong_user"><![CDATA[Извините, произошла ошибка. Вы не можете вступить в эту группу. Приглашение адресовано не Вам.]]></string>
+    <string name="_bx_groups_txt_n_unit"><![CDATA[{0} ({1})]]></string>
+    <string name="_bx_groups_txt_cart_item_title"><![CDATA[{0}: {1} ({2} {3})]]></string>
+
+    <string name="_bx_groups_msg_performed"><![CDATA[Действие было успешно выполнено.]]></string>
+    <string name="_bx_groups_err_price_duplicate"><![CDATA[Аналогичный ценовой план уже существует.]]></string>
+    <string name="_bx_groups_err_cannot_perform"><![CDATA[Невозможно выполнить действие. Пожалуйста, оповестите нас об этой проблеме.]]></string>
 
     <string name="_bx_groups_alert_action_fan_added"><![CDATA[присоединение нового участника]]></string>
     <string name="_bx_groups_txt_ntfs_fan_added"><![CDATA[В группу <b class="bx-ntfs-content-profile">{owner_name}</b> вступил <b class="bx-ntfs-content-entry">{subentry_sample}</b>]]></string>
@@ -301,6 +359,12 @@
     <string name="_bx_groups_role_administrator"><![CDATA[Администратор]]></string>
     <string name="_bx_groups_role_moderator"><![CDATA[Модератор]]></string>
 
+    <string name="_bx_groups_pre_lists_period_units"><![CDATA[Платежный интервал]]></string>
+    <string name="_bx_groups_period_unit_day"><![CDATA[день]]></string>
+    <string name="_bx_groups_period_unit_week"><![CDATA[неделя]]></string>
+    <string name="_bx_groups_period_unit_month"><![CDATA[месяц]]></string>
+    <string name="_bx_groups_period_unit_year"><![CDATA[год]]></string>
+
     <string name="_bx_groups_email_join_request"><![CDATA[Запрос на присоединение к группе]]></string>
     <string name="_bx_groups_email_join_request_subject"><![CDATA[Запрос на присоединение к группе]]></string>
     <string name="_bx_groups_email_join_request_body"><![CDATA[{email_header}

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

@@ -51,12 +51,14 @@
     <string name="_feature_err_duplicate_feature"><![CDATA[Данный объект уже добавлен в список продвинутых.]]></string>
     <string name="_feature_err_not_enabled"><![CDATA[Действие не доступно в данный момент. Пожалуйста, попробуйте позднее.]]></string>
 	<string name="_Female"><![CDATA[Женщина]]></string>
+        <string name="_free"><![CDATA[бесплатно]]></string>
 	<string name="_From"><![CDATA[От]]></string>
 	<string name="_Header"><![CDATA[Заголовок]]></string>
 	<string name="_Incorrect Captcha"><![CDATA[Неправильная капча]]></string>
 	<string name="_Incorrect Email"><![CDATA[Введите правильный адрес электронной почты]]></string>
 	<string name="_Info"><![CDATA[Инфо]]></string>
 	<string name="_Keyword"><![CDATA[Ключевое слово]]></string>
+        <string name="_lifetime"><![CDATA[пожизненно]]></string>
 	<string name="_LookinFemale"><![CDATA[Женщина]]></string>
 	<string name="_LookinMale"><![CDATA[Мужчина]]></string>
 	<string name="_MEMBERSHIP_EXPIRES_IN_DAYS"><![CDATA[окончание срока: через {0} дней]]></string>
@@ -2482,6 +2484,7 @@
     <string name="_sys_menu_item_title_sm_unfriend"><![CDATA[Удалить из друзей]]></string>
     <string name="_sys_menu_item_title_sm_unfriend_cancel"><![CDATA[Отменить запрос о дружбе]]></string>
     <string name="_sys_menu_item_title_sm_unfriend_reject"><![CDATA[Отвергнуть запрос о дружбе]]></string>
+    <string name="_sys_menu_item_title_sm_join_paid"><![CDATA[Оплатить и присоединиться]]></string>
     <string name="_sys_menu_item_title_sm_join"><![CDATA[Присоединиться]]></string>
     <string name="_sys_menu_item_title_sm_join_confirm"><![CDATA[Одобрить присоединение]]></string>
     <string name="_sys_menu_item_title_sm_leave"><![CDATA[Покинуть]]></string>
@@ -2566,6 +2569,7 @@
     <string name="_sys_menu_item_title_system_sm_actions"><![CDATA[Действия]]></string>
     <string name="_sys_menu_item_title_system_sm_befriend"><![CDATA[Добавить в друзья]]></string>
     <string name="_sys_menu_item_title_system_sm_unfriend"><![CDATA[Удалить из друзей]]></string>
+    <string name="_sys_menu_item_title_system_sm_join_paid"><![CDATA[Оплатить и присоединиться]]></string>
     <string name="_sys_menu_item_title_system_sm_join"><![CDATA[Присоединиться]]></string>
     <string name="_sys_menu_item_title_system_sm_leave"><![CDATA[Покинуть]]></string>
     <string name="_sys_menu_item_title_system_sm_nl"><![CDATA[Новая строка]]></string>
@@ -3025,6 +3029,7 @@
 	<string name="_sys_txt_login_invalid_role"><![CDATA[Доступ запрещён]]></string>
     <string name="_sys_txt_my_profiles"><![CDATA[Создать новый профайл]]></string>
     <string name="_sys_txt_new_picture"><![CDATA[Новая картинка]]></string>
+    <string name="_sys_txt_not_found"><![CDATA[Не найдено]]></string>
     <string name="_sys_txt_no_picture_uploaded"><![CDATA[Картинка не загружена]]></string>
     <string name="_sys_txt_no_title"><![CDATA[Без заголовка]]></string>
     <string name="_sys_txt_n_by"><![CDATA[{0}]]></string>

+ 0 - 1
modules/boonex/tasks/classes/BxTasksConfig.php

@@ -136,7 +136,6 @@ class BxTasksConfig extends BxBaseModTextConfig
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY' => 'bx_tasks_view', // actions menu on view entry page
             'OBJECT_MENU_ACTIONS_VIEW_ENTRY_ALL' => 'bx_tasks_view_actions', // all actions menu on view entry page
             'OBJECT_MENU_ACTIONS_MY_ENTRIES' => 'bx_tasks_my', // actions menu on my entries page
-            'OBJECT_MENU_SUBMENU' => 'bx_tasks_submenu', // main module submenu
             'OBJECT_MENU_SUBMENU_VIEW_ENTRY' => 'bx_tasks_view_submenu', // view entry submenu
             'OBJECT_MENU_SUBMENU_VIEW_ENTRY_MAIN_SELECTION' => 'tasks-home', // first item in view entry submenu from main module submenu
             'OBJECT_MENU_SNIPPET_META' => 'bx_tasks_snippet_meta', // menu for snippet meta info

+ 1 - 6
modules/boonex/tasks/install/sql/disable.sql

@@ -13,7 +13,7 @@ DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_tasks' OR `object` IN('bx_ta
 -- MENU
 DELETE FROM `sys_objects_menu` WHERE `module` = 'bx_tasks';
 DELETE FROM `sys_menu_sets` WHERE `module` = 'bx_tasks';
-DELETE FROM `sys_menu_items` WHERE `module` = 'bx_tasks' OR `set_name` IN('bx_tasks_create_task_attachments', 'bx_tasks_view', 'bx_tasks_view_actions', 'bx_tasks_submenu', 'bx_tasks_view_submenu', 'bx_tasks_snippet_meta', 'bx_tasks_my', 'bx_tasks_menu_manage_tools');
+DELETE FROM `sys_menu_items` WHERE `module` = 'bx_tasks' OR `set_name` IN('bx_tasks_create_task_attachments', 'bx_tasks_view', 'bx_tasks_view_actions', 'bx_tasks_view_submenu', 'bx_tasks_my', 'bx_tasks_menu_manage_tools');
 
 -- PRIVACY 
 DELETE FROM `sys_objects_privacy` WHERE `object` = 'bx_tasks_allow_view_to';
@@ -40,11 +40,6 @@ DELETE FROM `sys_statistics` WHERE `name` LIKE 'bx_tasks%';
 -- CHARTS
 DELETE FROM `sys_objects_chart` WHERE `object` LIKE 'bx_tasks%';
 
--- GRIDS
-DELETE FROM `sys_objects_grid` WHERE `object` IN ('bx_tasks_administration', 'bx_tasks_common');
-DELETE FROM `sys_grid_fields` WHERE `object` IN ('bx_tasks_administration', 'bx_tasks_common');
-DELETE FROM `sys_grid_actions` WHERE `object` IN ('bx_tasks_administration', 'bx_tasks_common');
-
 -- UPLOADERS
 DELETE FROM `sys_objects_uploader` WHERE `object` LIKE 'bx_tasks_%';
 

+ 0 - 34
modules/boonex/tasks/install/sql/enable.sql

@@ -83,14 +83,6 @@ INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `t
 ('bx_tasks_context', 1, 'bx_tasks', '_bx_tasks_page_block_title_sys_entries_in_context', '_bx_tasks_page_block_title_entries_in_context', 11, 2147483647, 'service', 'a:2:{s:6:\"module\";s:8:\"bx_tasks\";s:6:\"method\";s:14:\"browse_context\";}', 0, 0, 1, 1),
 ('bx_tasks_context', 1, 'bx_tasks', '_bx_tasks_page_block_title_sys_calendar_in_context', '_bx_tasks_page_block_title_calendar_in_context', 11, 2147483647, 'service', 'a:4:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:8:"calendar";s:12:"ignore_cache";b:1;s:6:"params";a:1:{i:0;a:1:{s:10:"context_id";s:12:"{profile_id}";}}}', 0, 0, 1, 2);
 
--- PAGE: module manage all
-INSERT INTO `sys_objects_page`(`object`, `title_system`, `title`, `module`, `layout_id`, `visible_for_levels`, `visible_for_levels_editable`, `uri`, `url`, `meta_description`, `meta_keywords`, `meta_robots`, `cache_lifetime`, `cache_editable`, `deletable`, `override_class_name`, `override_class_file`) VALUES 
-('bx_tasks_administration', '_bx_tasks_page_title_sys_manage_administration', '_bx_tasks_page_title_manage', 'bx_tasks', 5, 192, 1, 'tasks-administration', 'page.php?i=tasks-administration', '', '', '', 0, 1, 0, 'BxTasksPageBrowse', 'modules/boonex/tasks/classes/BxTasksPageBrowse.php');
-
-INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `order`) VALUES 
-('bx_tasks_administration', 1, 'bx_tasks', '_bx_tasks_page_block_title_system_manage_administration', '_bx_tasks_page_block_title_manage', 11, 192, 'service', 'a:3:{s:6:\"module\";s:8:\"bx_tasks\";s:6:\"method\";s:12:\"manage_tools\";s:6:\"params\";a:1:{i:0;s:14:\"administration\";}}', 0, 1, 0);
-
-
 -- MENU: create task form attachments (link, photo, video, etc)
 
 INSERT INTO `sys_objects_menu`(`object`, `title`, `set_name`, `module`, `template_id`, `deletable`, `active`, `override_class_name`, `override_class_file`) VALUES 
@@ -161,11 +153,6 @@ INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `titl
 ('bx_tasks_view_submenu', 'bx_tasks', 'view-task', '_bx_tasks_menu_item_title_system_view_entry', '_bx_tasks_menu_item_title_view_entry_submenu_entry', 'page.php?i=view-task&id={content_id}', '', '', '', '', 2147483647, 0, 0, 1),
 ('bx_tasks_view_submenu', 'bx_tasks', 'view-task-comments', '_bx_tasks_menu_item_title_system_view_entry_comments', '_bx_tasks_menu_item_title_view_entry_submenu_comments', 'page.php?i=view-task-comments&id={content_id}', '', '', '', '', 2147483647, 0, 0, 2);
 
--- MENU: dashboard manage tools
-SET @iManageMenuOrder = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_menu_items` WHERE `set_name`='sys_account_dashboard_manage_tools' LIMIT 1);
-INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `addon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
-('sys_account_dashboard_manage_tools', 'bx_tasks', 'tasks-administration', '_bx_tasks_menu_item_title_system_admt_tasks', '_bx_tasks_menu_item_title_admt_tasks', 'page.php?i=tasks-administration', '', '_self', 'tasks', 'a:2:{s:6:"module";s:8:"bx_tasks";s:6:"method";s:27:"get_menu_addon_manage_tools";}', '', 192, 1, 0, @iManageMenuOrder + 1);
-
 -- MENU: add menu item to profiles modules (trigger* menu sets are processed separately upon modules enable/disable)
 
 INSERT INTO `sys_menu_items`(`set_name`, `module`, `name`, `title_system`, `title`, `link`, `onclick`, `target`, `icon`, `submenu_object`, `visible_for_levels`, `active`, `copyable`, `order`) VALUES 
@@ -270,27 +257,6 @@ INSERT INTO `sys_objects_chart` (`object`, `title`, `table`, `field_date_ts`, `f
 ('bx_tasks_growth', '_bx_tasks_chart_growth', 'bx_tasks_tasks', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 1, 'BxDolChartGrowth', ''),
 ('bx_tasks_growth_speed', '_bx_tasks_chart_growth_speed', 'bx_tasks_tasks', 'added', '', 'status,status_admin', '', 1, @iMaxOrderCharts + 2, 'BxDolChartGrowthSpeed', '');
 
--- GRIDS: moderation tools
-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_tasks_administration', 'Sql', 'SELECT * FROM `bx_tasks_tasks` WHERE 1 ', 'bx_tasks_tasks', 'id', 'added', 'status_admin', '', 20, NULL, 'start', '', 'title,text', '', 'like', 'reports', '', 192, 'BxTasksGridAdministration', 'modules/boonex/tasks/classes/BxTasksGridAdministration.php');
-
-INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `order`) VALUES
-('bx_tasks_administration', 'checkbox', '_sys_select', '2%', 0, '', '', 1),
-('bx_tasks_administration', 'switcher', '_bx_tasks_grid_column_title_adm_active', '8%', 0, '', '', 2),
-('bx_tasks_administration', 'reports', '_sys_txt_reports_title', '5%', 0, '', '', 3),
-('bx_tasks_administration', 'title', '_bx_tasks_grid_column_title_adm_title', '25%', 0, '25', '', 4),
-('bx_tasks_administration', 'added', '_bx_tasks_grid_column_title_adm_added', '20%', 1, '25', '', 5),
-('bx_tasks_administration', 'author', '_bx_tasks_grid_column_title_adm_author', '20%', 0, '25', '', 6),
-('bx_tasks_administration', 'actions', '', '20%', 0, '', '', 7);
-
-
-INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
-('bx_tasks_administration', 'bulk', 'delete', '_bx_tasks_grid_action_title_adm_delete', '', 0, 1, 1),
-('bx_tasks_administration', 'single', 'edit', '_bx_tasks_grid_action_title_adm_edit', 'pencil-alt', 1, 0, 1),
-('bx_tasks_administration', 'single', 'delete', '_bx_tasks_grid_action_title_adm_delete', 'remove', 1, 1, 2),
-('bx_tasks_administration', 'single', 'settings', '_bx_tasks_grid_action_title_adm_more_actions', 'cog', 1, 0, 3),
-('bx_tasks_administration', 'single', 'audit_content', '_bx_tasks_grid_action_title_adm_audit_content', 'search', 1, 0, 4);
-
 -- UPLOADERS
 
 INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES

+ 0 - 4
modules/boonex/tasks/install/sql/install.sql

@@ -516,10 +516,6 @@ INSERT INTO `sys_objects_content_info` (`name`, `title`, `alert_unit`, `alert_ac
 ('bx_tasks', '_bx_tasks', 'bx_tasks', 'added', 'edited', 'deleted', '', ''),
 ('bx_tasks_cmts', '_bx_tasks_cmts', 'bx_tasks', 'commentPost', 'commentUpdated', 'commentRemoved', 'BxDolContentInfoCmts', '');
 
-INSERT INTO `sys_content_info_grids` (`object`, `grid_object`, `grid_field_id`, `condition`, `selection`) VALUES
-('bx_tasks', 'bx_tasks_administration', 'id', '', ''),
-('bx_tasks', 'bx_tasks_common', 'id', '', '');
-
 -- SEARCH EXTENDED
 INSERT INTO `sys_objects_search_extended` (`object`, `object_content_info`, `module`, `title`, `active`, `class_name`, `class_file`) VALUES
 ('bx_tasks', 'bx_tasks', 'bx_tasks', '_bx_tasks_search_extended', 1, '', ''),

+ 0 - 2
modules/boonex/tasks/install/sql/uninstall.sql

@@ -49,8 +49,6 @@ DELETE FROM `sys_objects_feature` WHERE `name` = 'bx_tasks';
 -- CONTENT INFO
 DELETE FROM `sys_objects_content_info` WHERE `name` IN ('bx_tasks', 'bx_tasks_cmts');
 
-DELETE FROM `sys_content_info_grids` WHERE `object` IN ('bx_tasks');
-
 -- SEARCH EXTENDED
 DELETE FROM `sys_objects_search_extended` WHERE `module` = 'bx_tasks';
 

+ 19 - 3
template/scripts/BxBaseFormView.php

@@ -70,6 +70,11 @@ class BxBaseFormView extends BxDolForm
      */
     protected $_bViewMode = false;
 
+    /**
+     * Show or not sections which have no fileds
+     */
+    protected $_bShowEmptySections = false;
+
     /**
      * Function name for generation close form section HTML.
      */
@@ -254,6 +259,11 @@ class BxBaseFormView extends BxDolForm
         ));
     }
 
+    function setShowEmptySections($b)
+    {
+        $this->_bShowEmptySections = $b;
+    }
+
     function setAjaxMode($bAjaxMode)
     {
         $this->_bAjaxMode = (bool)$bAjaxMode;
@@ -450,7 +460,7 @@ BLAH;
         $sFuncGenRow = isset($this->aParams['view_mode']) && $this->aParams['view_mode'] ? 'genViewRow' : 'genRow';
         foreach ($this->aInputs as $aInput) {
             if (!isset($aInput['visible_for_levels']) || self::isVisible($aInput)) {
-                if ('block_header' == $aInput['type']) {
+                if ('block_header' == $aInput['type'] && !$this->_bShowEmptySections) {
                     // don't show section with no fields or with empty fields
                     if ($sContHeader) {                        
                         if ($sContFields)
@@ -644,7 +654,10 @@ EOS;
                 if (empty($aInput['value']) || !$aInput['value'] || '0000-00-00' == $aInput['value'])
                     break;
 
-                $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE, true);
+                if (isset($aInput['db']['pass']) && ('DateUtc' == $aInput['db']['pass'] || 'DateTimeUtc' == $aInput['db']['pass']) && !is_numeric($aInput['value'])) 
+                    $sValue = BxTemplFunctions::getInstance()->timeForJsFullDate($aInput['value'] . " Z", isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE, true);
+                else
+                    $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE, true);
             break;
 
             case 'date_time':
@@ -653,7 +666,10 @@ EOS;
                 if(empty($aInput['value']) || !$aInput['value'] || '0000-00-00 00:00:00' == $aInput['value'] || '0000-00-00 00:00' == $aInput['value'])
                     break;
 
-                $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE_TIME, true);
+                if (isset($aInput['db']['pass']) && ('DateUtc' == $aInput['db']['pass'] || 'DateTimeUtc' == $aInput['db']['pass']) && !is_numeric($aInput['value'])) 
+                    $sValue = BxTemplFunctions::getInstance()->timeForJsFullDate($aInput['value'] . " Z", isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE_TIME, true);
+                else
+                    $sValue = BxTemplFunctions::getInstance()->{is_numeric($aInput['value']) ? 'timeForJs' : 'timeForJsFullDate'}($aInput['value'], isset($aInput['date_format']) ? $aInput['date_format'] : BX_FORMAT_DATE_TIME, true);
             break;
 
             case 'checkbox_set':