Alex Trofimov 13 سال پیش
والد
کامیت
96d9c68253

+ 336 - 0
inc/classes/BxDolConnection.php

@@ -0,0 +1,336 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolConnectionQuery');
+
+
+/**
+ * Default limit for connections lists
+ */
+define('BX_CONNECTIONS_LIST_LIMIT', 1000);
+
+
+/**
+ * Connections order: no order
+ */ 
+define('BX_CONNECTIONS_ORDER_NONE', 0);
+
+/**
+ * Connections order: by addded time, asceding
+ */
+define('BX_CONNECTIONS_ORDER_ADDED_ASC', 1);
+
+/**
+ * Connections order: by addded time, desceding
+ */
+define('BX_CONNECTIONS_ORDER_ADDED_DESC', 2);
+
+
+/**
+ * Connection type: one-way
+ */
+define('BX_CONNECTIONS_TYPE_ONE_WAY', 'one-way');
+
+/**
+ * Connection type: mutual
+ */
+define('BX_CONNECTIONS_TYPE_MUTUAL', 'mutual');
+
+
+/** 
+ * @page objects 
+ * @section connection Connection
+ * @ref BxDolConnection
+ */
+
+/**
+ * Connection is usefull when you need to organize some sorts of connections between different conetent, 
+ * for example: friends, contacts, favorites, block lists, subscriptions, etc.
+ * 
+ * Two types of connections are supported one way connections (blick list, favourites) and mutual (friends). 
+ *
+ *
+ * @section connection_create Creating the Connection object:
+ *
+ * Step 1:
+ * Add record to 'sys_objects_connection' table:
+ * - object: name of the connection object, in the format: vendor prefix, underscore, module prefix, underscore, internal identifier or nothing; 
+ *           for example: bx_blogs_favorites - favorite blogs for users in blogs module.
+ * - table: table name with connections, see step 2
+ * - type: 'one-way' or 'mutual'
+ * - override_class_name: user defined class name which is derived from one of base classes.
+ * - override_class_file: the location of the user defined class, leave it empty if class is located in system folders.
+ *
+ * Step 2:
+ * Create table for connections:
+ * @code
+ * CREATE TABLE `my_sample_connections` (
+ *   `id` int(11) NOT NULL AUTO_INCREMENT,
+ *   `initiator` int(11) NOT NULL,
+ *   `content` int(11) NOT NULL,
+ *   `mutual` tinyint(4) NOT NULL, -- can re removed for one-way connections 
+ *   `added` int(10) unsigned NOT NULL,
+ *   PRIMARY KEY (`id`),
+ *   UNIQUE KEY `initiator` (`initiator`,`content`),
+ *   KEY `content` (`content`)
+ * )
+ * @endcode
+ *
+ *
+ * @section example Example of usage
+ * 
+ * Check if two profiles are friends:
+ * @code
+ *   bx_import('BxDolConnection'); // import connection class
+ *   $oConnectionFriends = BxDolConnection::getObjectInstance('bx_profiles_friends'); // get friends connections object
+ *   if ($oConnectionFriends) // check if connections is available for using
+ *      echo $oConnectionFriends->isConnected (100, 200, true) ? "100 and 200 are friends" : "100 and 200 aren't friends"; // check if profiles with IDs 100 and 200 have mutual connections
+ * @endcode
+ *
+ * Get mutual content IDs (friends IDs)
+ * @code
+ *   bx_import('BxDolConnection'); // import connection class
+ *   $oConnectionFriends = BxDolConnection::getObjectInstance('bx_profiles_friends'); // get friends connections object
+ *   if ($oConnectionFriends) // check if connections is available for using
+ *       print_r($oConnection->getConnectedContent(100, 1)); // print array of friends IDs of 100's profile
+ * @endcode
+ * 
+ */
+class BxDolConnection extends BxDol {
+
+    protected $_sObject;
+    protected $_aObject;
+    protected $_oQuery;
+    protected $_sType;
+
+    /**
+     * Constructor
+     * @param $aObject array of connection options
+     */
+    protected function __construct($aObject) {
+        parent::__construct();
+
+        $this->_sObject = $aObject['object'];
+        $this->_aObject = $aObject;
+        $this->_sType = $aObject['type'];
+
+        $this->_oQuery = new BxDolConnectionQuery($aObject);
+    }
+
+    /**
+     * Get connection object instance by object name
+     * @param $sObject object name
+     * @return object instance or false on error
+     */
+    static public function getObjectInstance($sObject) {
+
+        if (!$sObject)
+            return false;
+
+        if (isset($GLOBALS['bxDolClasses']['BxDolConnection!'.$sObject]))
+            return $GLOBALS['bxDolClasses']['BxDolConnection!'.$sObject];
+        
+        $aObject = BxDolConnectionQuery::getConnectionObject($sObject);
+        if (!$aObject || !is_array($aObject))
+            return false;
+
+        $sClass = empty($aObject['override_class_name']) ? 'BxDolConnection' : $aObject['override_class_name'];
+        if (!empty($aObject['override_class_file']))
+            require_once(BX_DIRECTORY_PATH_ROOT . $aObject['override_class_file']);
+        else    
+            bx_import($sClass);
+        
+        $o = new $sClass($aObject);
+
+        return ($GLOBALS['bxDolClasses']['BxDolConnection!'.$sObject] = $o);
+    }
+
+
+    /**
+     * Add new connection.
+     * @param $iInitiator initiator of the connection, in most cases some profile id
+     * @param $iContent content to make connection to, in most cases some content id, or other profile id in case of friends
+     * @return true - if connection was added, false - if connection already exists or error occured
+     */ 
+    public function addConnection ($iInitiator, $iContent) {
+        $iMutual = 0;
+        if (!$this->_oQuery->addConnection((int)$iInitiator, (int)$iContent, $iMutual))
+            return false; 
+    
+        bx_alert($this->_sObject, 'connection_added', 0, getLoggedId(), array(
+            'initiator' => (int)$iInitiator, 
+            'content' => (int)$iContent, 
+            'mutual' => (int)$iMutual,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+    /**
+     * Remove connection.
+     * @param $iInitiator initiator of the connection
+     * @param $iContent connected content or other profile id in case of friends
+     * @return true - if connection was removed, false - if connection isn't exist or error occured
+     */
+    public function removeConnection ($iInitiator, $iContent) {
+        if (!($aConnection = $this->_oQuery->getConnection((int)$iInitiator, (int)$iContent))) // connection doesn't exist
+            return false;
+
+        if (!$this->_oQuery->removeConnection((int)$iInitiator, (int)$iContent))
+            return false;
+
+        bx_alert($this->_sObject, 'connection_removed', 0, getLoggedId(), array(
+            'initiator' => (int)$iInitiator, 
+            'content' => (int)$iContent, 
+            'mutual' => isset($aConnection['mutual']) ? $aConnection['mutual'] : 0,
+            'object' => $this,
+        ));
+
+        return true;
+    }
+
+    /**
+     * Get connected content IDs
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */    
+    public function getConnectedContent ($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE) {
+        return $this->_oQuery->getConnectedContent($iInitiator, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get connected initiators IDs
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of available connections
+     */    
+    public function getConnectedInitiators ($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE) {
+        return $this->_oQuery->getConnectedInitiators($iContent, $isMutual, $iStart, $iLimit, $iOrder);
+    }
+
+    /**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $iInitiator initiator of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedContentAsSQLParts ($sContentTable, $sContentField, $iInitiator, $isMutual = false) {
+        return $this->_oQuery->getConnectedContentSQLParts($sContentTable, $sContentField, $iInitiator, $isMutual);
+    }
+
+    /**
+     * Get necessary parts of SQL query to use connections in other queries
+     * @param $sContentTable content table or alias
+     * @param $sContentField content table field or field alias
+     * @param $iContent content of the connection
+     * @param $isMutual get mutual connections only
+     * @return array of SQL string parts, for now 'join' part only is returned
+     */
+    public function getConnectedInitiatorsAsSQLParts ($sContentTable, $sContentField, $iContent, $isMutual = false) {
+        return $this->_oQuery->getConnectedInitiatorsSQLParts($sContentTable, $sContentField, $iContent, $isMutual);
+    }
+
+    /**
+     * Get necessary condition array to use connections in search classes
+     * @param $sContentField content table field name
+     * @param $iInitiator initiator of the connection
+     * @param $iMutual get mutual connections only
+     * @return array of conditions, for now with 'restriction' and 'join' parts
+     */
+    public function getConnectedContentAsCondition ($sContentField, $iInitiator, $iMutual = false) {
+        return array(
+
+            'restriction' => array (
+                'connections_' . $this->_sObject => array(
+                    'value' => $iInitiator,
+                    'field' => 'initiator',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),
+                'connections_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),               
+            ),
+
+            'join' => array (
+                'connections_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'mainField' => $sContentField,
+                    'onField' => 'content',
+                    'joinFields' => array(),//'initiator'),
+                ),
+            ),
+
+        );        
+    }
+
+    /**
+     * Get necessary condition array to use connections in search classes
+     * @param $sContentField content table field name
+     * @param $iInitiator initiator of the connection
+     * @param $iMutual get mutual connections only
+     * @return array of conditions, for now with 'restriction' and 'join' parts
+     */
+    public function getConnectedInitiatorsAsCondition ($sContentField, $iContent, $iMutual = false) {
+        return array(
+
+            'restriction' => array (
+                'connections_' . $this->_sObject => array(
+                    'value' => $iContent,
+                    'field' => 'content',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),
+                'connections_mutual_' . $this->_sObject => array(
+                    'value' => $iMutual,
+                    'field' => 'mutual',
+                    'operator' => '=',
+                    'table' => $this->_aObject['table'],
+                ),               
+            ),
+
+            'join' => array (
+                'connections_' . $this->_sObject => array(
+                    'type' => 'INNER',
+                    'table' => $this->_aObject['table'],
+                    'mainField' => $sContentField,
+                    'onField' => 'initiator',
+                    'joinFields' => array(),//'initiator'),
+                ),
+            ),
+
+        );        
+    }    
+
+    /**
+     * Check if initiator and content are connected. 
+     * In case if friends this function in conjunction with isMutual parameter can be used to check pending friend requests.
+     * @param $iInitiator initiator of the connection
+     * @param $iContent connected content or other profile id in case of friends
+     * @return true - if content and initiator are connected or false - in all other cases
+     */    
+    public function isConnected ($iInitiator, $iContent, $isMutual = false) {
+        $oConnection = $this->_oQuery->getConnection ($iInitiator, $iContent);
+        if (!$oConnection)
+            return false;
+        return false === $isMutual ? true : $oConnection['mutual'];
+    }
+
+
+}
+
+/** @} */

+ 135 - 0
inc/classes/BxDolConnectionQuery.php

@@ -0,0 +1,135 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Dolphin Core
+ * @{
+ */
+
+bx_import('BxDolDb');
+
+/**
+ * Database queries for Connection objects.
+ * @see BxDolConnection
+ */
+class BxDolConnectionQuery extends BxDolDb {
+    protected $_aObject;
+    protected $_sTable;
+    protected $_sType;
+
+    public function __construct($aObject) {
+        parent::__construct();
+        $this->_aObject = $aObject;
+        $this->_sTable = $aObject['table'];
+        $this->_sType = $aObject['type'];
+    }
+
+    static public function getConnectionObject ($sObject) {
+        $oDb = BxDolDb::getInstance();
+        $sQuery = $oDb->prepare("SELECT * FROM `sys_objects_connection` WHERE `object` = ?", $sObject);
+        $aObject = $oDb->getRow($sQuery);
+        if (!$aObject || !is_array($aObject)) 
+            return false;
+        
+        return $aObject;
+    }
+
+    public function getConnectedContentSQLParts ($sContentTable, $sContentField, $iInitiator, $isMutual = false) {
+        $sWhere = $this->prepare(" AND `" . $this->_sTable . "`.`initiator` = ?", $iInitiator);
+        if (false !== $isMutual)
+            $sWhere .= $this->prepare(" AND `mutual` = ?", $isMutual);
+        return array(
+            'join' => "INNER JOIN `{$this->_sTable}` ON (`{$this->_sTable}`.`content` = `$sContentTable`.`$sContentField` $sWhere)",
+        );
+    }
+
+    public function getConnectedInitiatorsSQLParts ($sContentTable, $sContentField, $iInitiator, $isMutual = false) {
+        $sWhere = $this->prepare(" AND `" . $this->_sTable . "`.`content` = ?", $iInitiator);
+        if (false !== $isMutual)
+            $sWhere .= $this->prepare(" AND `mutual` = ?", $isMutual);
+        return array(
+            'join' => "INNER JOIN `{$this->_sTable}` ON (`{$this->_sTable}`.`initiator` = `$sContentTable`.`$sContentField` $sWhere)",
+        );
+    }
+
+    public function getConnectedContent ($iInitiator, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE) {
+        $sWhere = $this->prepare(" AND `initiator` = ?", $iInitiator);
+        $sQuery = $this->_getConnectionsQuery($sWhere, '`content`', $isMutual, $iStart, $iLimit, $iOrder);
+        return $this->getColumn($sQuery);
+    }
+
+    public function getConnectedInitiators ($iContent, $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE) {
+        $sWhere = $this->prepare(" AND `content` = ?", $iContent);
+        $sQuery = $this->_getConnectionsQuery($sWhere, '`initiator`', $isMutual, $iStart, $iLimit, $iOrder);
+        return $this->getColumn($sQuery);
+    }
+
+    protected function _getConnectionsQuery ($sWhere, $sFields = '*', $isMutual = false, $iStart = 0, $iLimit = BX_CONNECTIONS_LIST_LIMIT, $iOrder = BX_CONNECTIONS_ORDER_NONE) {
+        $sOrder = '';
+        switch ($iOrder) {
+        case BX_CONNECTIONS_ORDER_ADDED_ASC: 
+            $sOrder = 'ORDER BY `added` ASC';
+            break;
+        case BX_CONNECTIONS_ORDER_ADDED_DESC: 
+            $sOrder = 'ORDER BY `added` DESC';
+            break;
+        }
+
+        if (false !== $isMutual)
+            $sWhere .= $this->prepare(" AND `mutual` = ?", $isMutual);
+
+        return $this->prepare("SELECT $sFields FROM `" . $this->_sTable . "` WHERE 1 $sWhere $sOrder LIMIT ?, ?", $iStart, $iLimit);
+    }
+
+    public function getConnection ($iInitiator, $iContent) {
+        $sQuery = $this->prepare("SELECT * FROM `" . $this->_sTable . "` WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        return $this->getRow($sQuery);
+    }
+
+    public function addConnection ($iInitiator, $iContent, &$iMutualParam = null) {
+        if ($this->getConnection($iInitiator, $iContent)) // connection already exists
+            return false;
+
+        if (BX_CONNECTIONS_TYPE_MUTUAL == $this->_sType) {
+            $aConnectionMutual = $this->getConnection($iContent, $iInitiator);
+            $iMutual = $aConnectionMutual ? 1 : 0;
+        }
+
+        $sQuery = $this->prepare("INSERT INTO `" . $this->_sTable . "` SET `initiator` = ?, `content` = ?, `mutual` = ?, `added` = ?", $iInitiator, $iContent, $iMutual, time());
+        if (!$this->query($sQuery))
+            return false;
+
+        if ($iMutual) // in case of mutual connection update 'mutual' field
+            $this->updateConnectionMutual($iContent, $iInitiator, $iMutual);
+
+        if (null !== $iMutualParam)
+            $iMutualParam = $iMutual;
+
+        return true;
+    }
+
+    public function updateConnectionMutual ($iInitiator, $iContent, $iMutual) {
+        $sQuery = $this->prepare("UPDATE `" . $this->_sTable . "` SET `mutual` = ? WHERE `initiator` = ? AND `content` = ?", $iMutual, $iInitiator, $iContent);
+        return $this->query($sQuery);
+    }
+
+    public function removeConnection ($iInitiator, $iContent) {
+        if (!($aConnection = $this->getConnection($iInitiator, $iContent))) // connection doesn't exist
+            return false;
+
+        $sQuery = $this->prepare("DELETE FROM `" . $this->_sTable . "` WHERE `initiator` = ? AND `content` = ?", $iInitiator, $iContent);
+        if (!$this->query($sQuery))
+            return false;
+
+        if (BX_CONNECTIONS_TYPE_MUTUAL == $this->_sType && $aConnection['mutual']) {
+            if (!$this->removeConnection($iContent, $iInitiator)) // in case of mutual connection - remove both connections
+                return false;
+        }
+
+        return true;
+    }
+
+}
+
+/** @} */

+ 1 - 1
inc/classes/BxDolSearch.php

@@ -517,7 +517,7 @@ class BxDolSearchResult {
             $aWhere[] = '1';
             foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
                 $sqlCondition = '';
-                if (isset($aValue['operator']) && !empty($aValue['value'])) {
+                if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
                    $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
                    $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
                    switch ($aValue['operator']) {

+ 41 - 0
install/sql/v70.sql

@@ -3052,6 +3052,47 @@ INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `conf
 -- --------------------------------------------------------
 
 
+CREATE TABLE IF NOT EXISTS `sys_objects_connection` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `object` varchar(32) NOT NULL,
+  `table` varchar(255) NOT NULL,
+  `type` enum('one-way','mutual') NOT NULL,
+  `override_class_name` varchar(255) NOT NULL,
+  `override_class_file` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `object` (`object`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+INSERT INTO `sys_objects_connection` (`object`, `table`, `type`, `override_class_name`, `override_class_file`) VALUES
+('sys_profiles_friends', 'sys_profiles_conn_friends', 'mutual', '', ''),
+('sys_profiles_contacts', 'sys_profiles_conn_contacts', 'one-way', '', '');
+
+
+CREATE TABLE IF NOT EXISTS `sys_profiles_conn_contacts` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `initiator` int(11) NOT NULL,
+  `content` int(11) NOT NULL,
+  `added` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `initiator` (`initiator`,`content`),
+  KEY `content` (`content`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `sys_profiles_conn_friends` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `initiator` int(11) NOT NULL,
+  `content` int(11) NOT NULL,
+  `mutual` tinyint(4) NOT NULL,
+  `added` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `initiator` (`initiator`,`content`),
+  KEY `content` (`content`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+-- --------------------------------------------------------
+
+
 CREATE TABLE IF NOT EXISTS `sys_objects_transcoder_images` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `object` varchar(32) NOT NULL,

+ 16 - 2
modules/boonex/persons/classes/BxPersonsModule.php

@@ -70,11 +70,25 @@ class BxPersonsModule extends BxDolModule {
         return $this->_serviceBrowse ('recent');
     }
 
-    public function _serviceBrowse ($sMode, $aParams = false) {
-        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
+    public function serviceBrowseConnections ($iProfileId, $sObjectConnections = 'sys_profiles_friends', $sConnectionsType = 'content', $iMutual = false, $iDesignBox = BX_DB_PADDING_DEF) {
+        return $this->_serviceBrowse (
+            'connections', 
+            array(
+                'object' => $sObjectConnections,
+                'type' => $sConnectionsType,
+                'mutual' => $iMutual,
+                'profile' => (int)$iProfileId),  
+            $iDesignBox
+        );
+    }
+
+    public function _serviceBrowse ($sMode, $aParams = false, $iDesignBox = BX_DB_PADDING_DEF) {
         bx_import('SearchResult', $this->_aModule);
+        $sClass = $this->_aModule['class_prefix'] . 'SearchResult';
         $o = new $sClass($sMode, $aParams);
 
+        $o->setDesignBoxTemplateId($iDesignBox);
+
         if ($o->isError)
             return false;
 

+ 36 - 16
modules/boonex/persons/classes/BxPersonsSearchResult.php

@@ -16,25 +16,25 @@ class BxPersonsSearchResult extends BxDolTwigSearchResult {
     var $aCurrent = array(
         'name' => 'bx_persons',
         'title' => '_bx_persons_page_title_browse',
-        'table' => 'bx_persons_data',
-        'ownFields' => array('id', 'fullname', 'picture', 'added'),
+        'table' => 'sys_profiles',
+        'ownFields' => array(),
         'searchFields' => array('fullname'),
         'restriction' => array(
-            'profile' => array('value' => 'bx_persons', 'field' => 'type', 'operator' => '=', 'table' => 'sys_profiles'),
-            'activeStatus' => array('value' => 'active', 'field' => 'status', 'operator' => '=', 'table' => 'sys_profiles'),
-            'owner' => array('value' => '', 'field' => 'author', 'operator' => '='),
+            'perofileStatus' => array('value' => 'active', 'field' => 'status', 'operator' => '='),
+            'perofileType' => array('value' => 'bx_persons', 'field' => 'type', 'operator' => '='),
+            'owner' => array('value' => '', 'field' => 'author', 'operator' => '=', 'table' => 'bx_persons_data'),
         ),
         'join' => array (
             'profile' => array(
                 'type' => 'INNER',
-                    'table' => 'sys_profiles',
-                    'mainField' => 'id',
-                    'onField' => 'content_id',
-                    'joinFields' => array('status'),
+                'table' => 'bx_persons_data',
+                'mainField' => 'content_id',
+                'onField' => 'id',
+                'joinFields' => array('id', 'fullname', 'picture', 'added'),
             ),
         ),
         'paginate' => array('perPage' => 20, 'start' => 0),
-        'sorting' => 'last',
+        'sorting' => 'none',
         'rss' => array(
             'title' => '',
             'link' => '',
@@ -52,14 +52,31 @@ class BxPersonsSearchResult extends BxDolTwigSearchResult {
         'ident' => 'id'
     );
 
-
-    function __construct($sMode = '', $sValue = '', $sValue2 = '', $sValue3 = '') {
+    function __construct($sMode = '', $mixedParams = false) {
 
         $oModuleMain = $this->getMain();
 
         switch ($sMode) {
 
+            case 'connections':
+                $aParams = $mixedParams;
+                bx_import('BxDolConnection');
+                $oConnection = isset($aParams['object']) ? BxDolConnection::getObjectInstance($aParams['object']) : false;
+                if ($oConnection && isset($aParams['profile']) && (int)$aParams['profile']) {
+
+                    $sMethod = 'getConnectedContentAsCondition';
+                    if (isset($aParams['type']) && $aParams['type'] == 'initiators')
+                        $sMethod = 'getConnectedInitiatorsAsCondition';
+
+                    $a = $oConnection->$sMethod('id', (int)$aParams['profile'], isset($aParams['mutual']) ? $aParams['mutual'] : false);
+                    $this->aCurrent['restriction'] = array_merge($this->aCurrent['restriction'], $a['restriction']);
+                    $this->aCurrent['join'] = array_merge($this->aCurrent['join'], $a['join']);
+
+                }
+                break;
+
             case 'search':
+                $sValue = $mixedParams;
                 if ($sValue)
                     $this->aCurrent['restriction']['keyword'] = array('value' => $sValue,'field' => '','operator' => 'against');
 
@@ -69,14 +86,14 @@ class BxPersonsSearchResult extends BxDolTwigSearchResult {
                 break;
 
             case 'user':
-                $iProfileId = (int)$sValue;                
+                $iProfileId = (int)$mixedParams;
                 $aContentInfo = $oModuleMain->_oDb->getContentInfoById($iContentId);
                 if (!$aContentInfo)
                     $this->isError = true;
                 else
                     $this->aCurrent['restriction']['owner']['value'] = $iProfileId;
 
-                $this->sBrowseUrl = "browse/user/$sValue";
+                $this->sBrowseUrl = "browse/user/$iProfileId";
                 $this->aCurrent['title'] = _t('_bx_persons_page_title_browse_by_author', $aContentInfo['fullname']); // TODO: owner name here
                 if (bx_get('rss')) {
                     $aData = getProfileInfo($iProfileId);
@@ -112,12 +129,15 @@ class BxPersonsSearchResult extends BxDolTwigSearchResult {
     }
 
     function getAlterOrder() {
-        if ($this->aCurrent['sorting'] == 'last') {
+        switch ($this->aCurrent['sorting']) {
+        case 'none':
+            return array();
+        case 'last':
+        default:
             $aSql = array();
             $aSql['order'] = " ORDER BY `bx_persons_data`.`added` DESC";
             return $aSql;
         }
-        return array();
     }
 
     function displayResultBlock () {        

+ 212 - 0
samples/connections.php

@@ -0,0 +1,212 @@
+<?php
+/**
+ * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
+ * CC-BY License - http://creativecommons.org/licenses/by/3.0/
+ *
+ * @defgroup    DolphinCore Samples
+ * @{
+ */
+
+/** 
+ * @page samples
+ * @section connections Connections
+ */
+
+/**
+ * This sample uses profile friends as sample connections.
+ * 
+ * @section usage $_GET params:
+ * 
+ * Display connections:
+ * - id: profile ID to display connectiond for
+ * - method = array: display connections using array method, just plain list of connections IDs
+ * - method = sql: display connections using SQL method, custom SQL query where connection data is inserted as part of SQL query
+ * - method = search-results: display connections using SearchResults class, SearchResults class must support connections
+ *
+ * Generate connections:
+ * - action = gen: to generate sample connections for all profiles, by default around ~2 friends for each profile, and around ~1 friend request
+ */ 
+
+require_once('./../inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "params.inc.php");
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLanguages');
+bx_import('BxDolTemplate');
+
+$oTemplate = BxDolTemplate::getInstance();
+$oTemplate->setPageNameIndex (BX_PAGE_DEFAULT);
+$oTemplate->setPageHeader ("Connections");
+$oTemplate->setPageContent ('page_main_code', PageCompMainCode());
+$oTemplate->getPageCode();
+
+/**
+ * page code function
+ */
+function PageCompMainCode() {
+    $oDb = BxDolDb::getInstance();
+
+    // config: for data generation
+    $iMutual = 2; // around 2 mutual connections for each profile
+    $iOneWay = 1; // around 1 one-way connections for each profile
+    $sTable = "`sys_profiles_conn_friends`"; // to clean table before inserting genrated data
+
+    // config: for displaying and data generation
+    $sType = 'bx_persons'; // profiles type
+    $sObject = 'sys_profiles_friends'; // connections object
+
+
+    if ('gen' == bx_get('action') || (isset($_SERVER['argv'][1]) && 'gen' == $_SERVER['argv'][1])) {
+        echo "\nConnections Generation:  <br />\n";
+        GenerateData($iMutual, $iOneWay, $sTable, $sType, $sObject);
+        echo "\nData has been generated";
+        exit;
+    }
+
+
+    ob_start();
+
+    $sMethod = bx_get('method');
+
+    $iProfileId = bx_get('id');
+    if (!$iProfileId) {
+        $sQueryOrig = "SELECT `id` FROM `sys_profiles` WHERE `type` = ? AND `status` = 'active' ORDER BY RAND() LIMIT 1";
+        $sQueryPrepared = $oDb->prepare($sQueryOrig, $sType);
+        $iProfileId = $oDb->getOne($sQueryPrepared);
+    }
+
+    bx_import('BxDolConnection');
+    $oConnection = BxDolConnection::getObjectInstance($sObject);
+    if (!$oConnection)
+        die ("'$sObject' object is not defined.");
+
+    echo "<h1>Profile: $iProfileId</h1>";
+    echo '<hr class="bx-def-hr" />';
+
+    switch($sMethod) {
+
+        default:
+        case 'array':
+
+            echo "<h2>Mutual Content (like Friends)</h2>";
+            echoDbg($oConnection->getConnectedContent($iProfileId, 1));
+    
+            echo "<h2>Connected Content</h2>";
+            echoDbg($oConnection->getConnectedContent($iProfileId));
+
+            echo "<h2>Connected Initiators</h2>";
+            echoDbg($oConnection->getConnectedInitiators($iProfileId));
+
+            echo "<h2>Connected Content without mutual content (like Friend Requests sent)</h2>";
+            echoDbg($oConnection->getConnectedContent($iProfileId, 0));
+
+            echo "<h2>Connected Initiators without mutual content (like Friend Requests received)</h2>";
+            echoDbg($oConnection->getConnectedInitiators($iProfileId, 0));
+            
+        break;
+        case 'sql':
+
+            $f = function ($aSQLParts) use ($oDb, $sType) {
+                $sQueryOrig = "
+                    SELECT `p`.`id`, `d`.`fullname` 
+                    FROM `bx_persons_data` AS `d`
+                    INNER JOIN `sys_profiles` AS `p` ON (`p`.`content_id` = `d`.`id` AND `p`.`type` = ?)
+                    {$aSQLParts['join']}
+                ";
+                $sQueryPrepared = $oDb->prepare($sQueryOrig, $sType);
+                $a = $oDb->getAll($sQueryPrepared);
+                foreach ($a as $r)
+                    echo "{$r['id']} - {$r['fullname']} <br />\n";
+            };
+
+            echo "<h2>Mutual Content (like Friends)</h2>";
+            $f($oConnection->getConnectedContentAsSQLParts('p', 'id', $iProfileId, 1));
+
+
+            echo "<h2>Connected Content</h2>";
+            $f($oConnection->getConnectedContentAsSQLParts('p', 'id', $iProfileId));
+
+
+            echo "<h2>Connected Initiators</h2>";
+            $f($oConnection->getConnectedInitiatorsAsSQLParts('p', 'id', $iProfileId));
+
+            echo "<h2>Connected Content without mutual content (like Friend Requests sent)</h2>";
+            $f($oConnection->getConnectedContentAsSQLParts('p', 'id', $iProfileId, 0));
+
+            echo "<h2>Connected Initiators without mutual content (like Friend Requests received)</h2>";
+            $f($oConnection->getConnectedInitiatorsAsSQLParts('p', 'id', $iProfileId, 0));
+
+        break;
+        case 'search-results':
+
+            echo "<h2>Mutual Content (like Friends)</h2>";
+            echo BxDolService::call('bx_persons', 'browse_connections', array($iProfileId, 'sys_profiles_friends', 'content', 1, BX_DB_CONTENT_ONLY));
+
+            echo "<h2>Connected Content</h2>";
+            echo BxDolService::call('bx_persons', 'browse_connections', array($iProfileId, 'sys_profiles_friends', 'content', false, BX_DB_CONTENT_ONLY));
+
+            echo "<h2>Connected Initiators</h2>";
+            echo BxDolService::call('bx_persons', 'browse_connections', array($iProfileId, 'sys_profiles_friends', 'initiators', false, BX_DB_CONTENT_ONLY));
+
+            echo "<h2>Connected Content without mutual content (like Friend Requests sent)</h2>";
+            echo BxDolService::call('bx_persons', 'browse_connections', array($iProfileId, 'sys_profiles_friends', 'content', 0, BX_DB_CONTENT_ONLY));
+
+            echo "<h2>Connected Initiators without mutual content (like Friend Requests received)</h2>";
+            echo BxDolService::call('bx_persons', 'browse_connections', array($iProfileId, 'sys_profiles_friends', 'initiators', 0, BX_DB_CONTENT_ONLY));
+            
+        break;
+    }
+
+    $s = ob_get_clean();
+    return DesignBoxContent("Connections", $s, BX_DB_PADDING_DEF);
+}
+
+function GenerateData($iMutual = 3, $iOneWay = 1, $sTable, $sType, $sObject) {
+    $oDb = BxDolDb::getInstance();
+
+    bx_import('BxDolConnection');
+    $oConnection = BxDolConnection::getObjectInstance($sObject);
+    if (!$oConnection)
+        die ("'$sObject' object is not defined.");
+
+    $oDb->query("TRUNCATE TABLE $sTable");
+
+    // get all profiles
+    $sQueryOrig = "SELECT * FROM `sys_profiles` WHERE `type` = ? AND `status` = 'active'";
+    $sQueryPrepared = $oDb->prepare($sQueryOrig, $sType);
+    if (!($aAll = $oDb->getAll($sQueryPrepared)))
+        die($oDb->getErrorMessage());
+
+    foreach ($aAll as $aProfile) {
+
+        // get random profiles to add as connections
+        $sQueryOrig = "SELECT * FROM `sys_profiles` WHERE `type` = ? AND `status` = 'active' AND `id` != ? ORDER BY RAND() LIMIT ?";
+        $sQueryPrepared = $oDb->prepare($sQueryOrig, $sType, $aProfile['id'], $iMutual + $iOneWay);
+        if (!($a = $oDb->getAll($sQueryPrepared)))
+            die($oDb->getErrorMessage());
+
+        $i = 0;
+        foreach ($a as $r) {
+
+            echo "{$aProfile['id']} " . ($i < $iMutual ? '&lt;=&gt;' : '=&gt;') . " {$r['id']}  <br />\n";
+
+            $aQueries = array();
+            if ($i < $iMutual) {
+                // mutual
+                if (!$oConnection->isConnected($aProfile['id'], $r['id'], true)) {
+                    $oConnection->addConnection($aProfile['id'], $r['id']);
+                    $oConnection->addConnection($r['id'], $aProfile['id']);
+                }
+            } elseif ($i >= $iMutual) {
+                // one-way
+                if (!$oConnection->isConnected($aProfile['id'], $r['id'])) 
+                    $oConnection->addConnection($aProfile['id'], $r['id']);
+            }
+
+            $i++;
+        }
+    }
+
+}
+
+/** @} */

+ 1 - 1
template/scripts/BxBaseSearchResult.php

@@ -12,7 +12,7 @@
 bx_import('BxDolSearch');
 class BxBaseSearchResult extends BxDolSearchResult {
 
-    protected $iDesignBoxTemplate = 11;
+    protected $iDesignBoxTemplate = BX_DB_PADDING_DEF;
     protected $aPermalinks;
     protected $aConstants;