Просмотр исходного кода

#24 - Timeline
1. 'Post a Photo' tab in 'Post to Timeline' block.
2. Inline comments for all type of events.

AntonLV 13 лет назад
Родитель
Сommit
e86d67db11

+ 16 - 7
modules/boonex/notes/classes/BxNotesModule.php

@@ -191,8 +191,9 @@ class BxNotesModule extends BxDolModule {
     		return '';
 
     	bx_import('BxDolPermalinks');
-		$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=view-note&id=' . $aContentInfo['id']);
+		$sUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=view-note&id=' . $aContentInfo[BxNotesConfig::$FIELD_ID]);
 
+		//--- Image(s)
 	    $sImage = '';
         if($aContentInfo[BxNotesConfig::$FIELD_THUMB]) {
         	bx_import('BxDolStorage');
@@ -201,25 +202,33 @@ class BxNotesModule extends BxDolModule {
 				$sImage = $oStorage->getFileUrlById($aContentInfo[BxNotesConfig::$FIELD_THUMB]);
         }
 
-        $aComments = array(); 
+        //--- Comments
         bx_import('BxDolCmts');
         $oCmts = BxDolCmts::getObjectInstance(BxNotesConfig::$OBJECT_COMMENTS, $aEvent['object_id']);
+
+        $aComments = array(); 
 		if($oCmts->isEnabled())
 			$aComments = array(
-				'count' => $aContentInfo['comments'],
-    			'url' => $oCmts->getListUrl()
+				'system' => BxNotesConfig::$OBJECT_COMMENTS,
+				'object_id' => $aContentInfo[BxNotesConfig::$FIELD_ID],
+				'count' => $aContentInfo['comments']
 			);
 
     	return array(
     		'owner_id' => $aContentInfo[BxNotesConfig::$FIELD_AUTHOR],
-    		'content_type' => !empty($sImage) ? 'image' : 'text', //how to parse content, 'text' by default.
+    		'content_type' => !empty($sImage) ? 'photo' : 'text', //how to parse content, 'text' by default.
     		'content' => array(
     			'url' => $sUrl,
     			'title' => $aContentInfo[BxNotesConfig::$FIELD_TITLE],
     			'text' => $aContentInfo[BxNotesConfig::$FIELD_TEXT],
-    			'image' => $sImage
+    			'images' => array(
+    				array(
+    					'url' => $sUrl,
+    					'src' => $sImage
+    				)
+    			)
     		), //a string to display or array to parse default template before displaying.
-    		'comments'=> $aComments,
+    		'comments' => $aComments,
     		'title' => '', //may be empty.
     		'description' => '' //may be empty. 
     	);

+ 37 - 0
modules/boonex/timeline/classes/BxTimelineConfig.php

@@ -30,6 +30,10 @@ class BxTimelineConfig extends BxDolModuleConfig
     protected $_aHandlers;
     protected $_aHandlersHidden;
 
+    protected $_sStorageObject;
+    protected $_sTranscoderObjectPreview;
+    protected $_aImageUploaders;
+
     protected $_bJsMode;
     protected $_aJsClass;
     protected $_aJsObjects;
@@ -54,6 +58,10 @@ class BxTimelineConfig extends BxDolModuleConfig
         $this->_aHandlersHidden = array();
         $this->_aHandlers = array();
 
+        $this->_sStorageObject = 'bx_timeline_photos';
+        $this->_sTranscoderObjectPreview = 'bx_timeline_photos_preview';
+        $this->_aImageUploaders = array('bx_timeline_simple');        
+
         $this->_bJsMode = false;
         $this->_aJsClass = array(
         	'main' => 'BxTimelineMain',
@@ -143,6 +151,35 @@ class BxTimelineConfig extends BxDolModuleConfig
         return $sResult;
     }
 
+    public function getObject($sType)
+    {
+    	$sResult = '';
+
+    	switch($sType) {
+    		case 'storage':
+    			$sResult = $this->_sStorageObject;
+    			break;
+    		case 'transcoder_preview':
+    			$sResult = $this->_sTranscoderObjectPreview;
+    			break;
+    	}
+
+        return $sResult;
+    }
+
+    public function getUploaders($sType)
+    {
+    	$aResult = '';
+
+    	switch($sType) {
+    		case 'image':
+    			$aResult = $this->_aImageUploaders;
+    			break;
+    	}
+
+        return $aResult; 
+    }
+
     public function getPerPage($sPage = 'profile')
     {
         $iResult = 10;

+ 51 - 53
modules/boonex/timeline/classes/BxTimelineDb.php

@@ -24,13 +24,13 @@ class BxTimelineDb extends BxDolModuleDb
         $this->_oConfig = $oConfig;
     }
 
-    function getAlertHandlerId()
+    public function getAlertHandlerId()
     {
     	$sQuery = $this->prepare("SELECT `id` FROM `sys_alerts_handlers` WHERE `name`=? LIMIT 1", $this->_oConfig->getSystemName('alert'));
         return (int)$this->getOne($sQuery);
     }
 
-    function insertData($aData)
+    public function insertData($aData)
     {
         foreach($aData['handlers'] as $aHandler) {
             //--- Delete module related events ---//
@@ -73,7 +73,7 @@ class BxTimelineDb extends BxDolModuleDb
         }
     }
 
-    function deleteData($aData)
+    public function deleteData($aData)
     {
         foreach($aData['handlers'] as $aHandler) {
             //--- Delete module related events ---//
@@ -105,7 +105,7 @@ class BxTimelineDb extends BxDolModuleDb
         }
     }
 
-    function getHandlers($aParams = array())
+    public function getHandlers($aParams = array())
     {
         $sMethod = 'getAll';
         $sWhereClause = '';
@@ -125,7 +125,7 @@ class BxTimelineDb extends BxDolModuleDb
         return $this->$sMethod($sSql);
     }
 
-    function insertEvent($aParamsSet)
+    public function insertEvent($aParamsSet)
     {
     	if(empty($aParamsSet))
     		return 0;
@@ -148,7 +148,7 @@ class BxTimelineDb extends BxDolModuleDb
 
         return $iId;
     }
-    function updateEvent($aParamsSet, $aParamsWhere)
+    public function updateEvent($aParamsSet, $aParamsWhere)
     {
     	if(empty($aParamsSet) || empty($aParamsWhere))
     		return false;
@@ -165,7 +165,7 @@ class BxTimelineDb extends BxDolModuleDb
         return $this->query($sSql);
     }
 
-    function deleteEvent($aParams, $sWhereAddon = "")
+    public function deleteEvent($aParams, $sWhereAddon = "")
     {
         $aWhere = array();
         foreach($aParams as $sKey => $sValue)
@@ -175,7 +175,7 @@ class BxTimelineDb extends BxDolModuleDb
         return $this->query($sSql);
     }
 
-    function getEvents($aParams)
+    public function getEvents($aParams)
     {
     	$sMethod = 'getAll';
         $sJoinClause = $sWhereClause = $sOrderClause = $sLimitClause = "";
@@ -213,7 +213,7 @@ class BxTimelineDb extends BxDolModuleDb
                 $sWhereClause .= isset($aParams['filter']) ? $this->_getFilterAddon($aParams['owner_id'], $aParams['filter']) : '';
                 $sWhereClause .= $sWhereModuleFilter;
                 $sOrderClause = isset($aParams['order']) ? "ORDER BY `te`.`date` " . strtoupper($aParams['order']) : "";
-                $sLimitClause = isset($aParams['count']) ? "LIMIT " . $aParams['start'] . ", " . $aParams['count'] : "";
+                $sLimitClause = isset($aParams['per_page']) ? "LIMIT " . $aParams['start'] . ", " . $aParams['per_page'] : "";
                 break;
 
             case 'last':
@@ -265,6 +265,48 @@ class BxTimelineDb extends BxDolModuleDb
 
         return $aEvent['day_date'] == $aEvent['day_now'] ? (int)floor($aEvent['ago_days']) : (int)ceil($aEvent['ago_days']);
     }
+
+	public function savePhoto($iEventId, $iPhId, $sPhTitle, $sPhText)
+	{
+		$sQuery = $this->prepare("INSERT INTO `" . $this->_sPrefix . "photos2events` SET `event_id`=?, `photo_id`=?, `title`=?, `text`=?", $iEventId, $iPhId, $sPhTitle, $sPhText);
+		return (int)$this->query($sQuery) > 0;
+	}
+
+	public function deletePhotos($iEventId)
+	{
+		$sQuery = $this->prepare("DELETE FROM `" . $this->_sPrefix . "photos2events` WHERE `event_id` = ?", $iEventId);
+		return (int)$this->query($sQuery) > 0;
+	}
+
+	public function getPhotos($iEventId)
+	{
+		$sQuery = $this->prepare("SELECT
+				 `tpe`.`photo_id` AS `id`,
+				 `tpe`.`title` AS `title`,
+				 `tpe`.`text` AS `text`
+			FROM `" . $this->_sPrefix . "photos2events` AS `tpe` 
+			LEFT JOIN `" . $this->_sPrefix . "photos` AS `tp` ON `tpe`.`photo_id` = `tp`.`id` 
+			WHERE `tpe`.`event_id` = ?", $iEventId);
+
+		return $this->getAll($sQuery);
+	}
+
+	protected function _getFilterAddon($iOwnerId, $sFilter)
+    {
+        switch($sFilter) {
+            case BX_TIMELINE_FILTER_OWNER:
+                $sFilterAddon = " AND `te`.`action`='' AND `te`.`object_id`='" . $iOwnerId . "' ";
+                break;
+            case BX_TIMELINE_FILTER_OTHER:
+                $sFilterAddon = " AND `te`.`action`='' AND `te`.`object_id`<>'" . $iOwnerId . "' ";
+                break;
+            case BX_TIMELINE_FILTER_ALL:
+            default:
+                $sFilterAddon = "";
+        }
+        return $sFilterAddon;
+    }
+
     
     
     
@@ -272,34 +314,7 @@ class BxTimelineDb extends BxDolModuleDb
     
     
     
-    
-    function deleteEventCommon($aParams)
-    {
-        return $this->deleteEvent($aParams, " AND `type` LIKE '" . $this->_oConfig->getCommonPostPrefix() . "%'");
-    }
-    function getUser($mixed, $sType = 'id')
-    {
-        switch($sType) {
-            case 'id':
-                $sWhereClause = "`ID`='" . $mixed . "'";
-                break;
-            case 'username':
-                $sWhereClause = "`NickName`='" . $mixed . "'";
-                break;
-        }
-
-        $sSql = "SELECT `ID` AS `id`, `Couple` AS `couple`, `NickName` AS `username`, `Password` AS `password`, `Email` AS `email`, `Sex` AS `sex`, `Status` AS `status` FROM `Profiles` WHERE " . $sWhereClause . " LIMIT 1";
-        $aUser = $this->getRow($sSql);
-
-        if(empty($aUser))
-            $aUser = array('id' => 0, 'couple' => 0, 'username' => _t('_wall_anonymous'), 'password' => '', 'email' => '', 'sex' => 'male');
-
-        return $aUser;
-    }
-
     //--- View Events Functions ---//
-
-
     function getEventsCount($iOwnerId, $sFilter, $sTimeline, $aModules)
     {
         $sWhereClause = "";
@@ -391,23 +406,6 @@ class BxTimelineDb extends BxDolModuleDb
         $sSql = "SELECT `Categories` FROM `" . $aType2Db[$sType]['table'] . "` WHERE `" . $aType2Db[$sType]['id'] . "`='" . $iId . "' LIMIT 1";
         return $this->getOne($sSql);
     }
-
-    //--- Private functions ---//
-    function _getFilterAddon($iOwnerId, $sFilter)
-    {
-        switch($sFilter) {
-            case BX_TIMELINE_FILTER_OWNER:
-                $sFilterAddon = " AND `te`.`action`='' AND `te`.`object_id`='" . $iOwnerId . "' ";
-                break;
-            case BX_TIMELINE_FILTER_OTHER:
-                $sFilterAddon = " AND `te`.`action`='' AND `te`.`object_id`<>'" . $iOwnerId . "' ";
-                break;
-            case BX_TIMELINE_FILTER_ALL:
-            default:
-                $sFilterAddon = "";
-        }
-        return $sFilterAddon;
-    }
 }
 
 /** @} */ 

+ 138 - 18
modules/boonex/timeline/classes/BxTimelineModule.php

@@ -26,7 +26,7 @@ define('BX_TIMELINE_DIVIDER_ID', ',');
 
 define('BX_TIMELINE_PARSE_TYPE_TEXT', 'text');
 define('BX_TIMELINE_PARSE_TYPE_LINK', 'link');
-define('BX_TIMELINE_PARSE_TYPE_IMAGE', 'image');
+define('BX_TIMELINE_PARSE_TYPE_PHOTO', 'photo');
 define('BX_TIMELINE_PARSE_TYPE_DEFAULT', BX_TIMELINE_PARSE_TYPE_TEXT);
 
 
@@ -91,6 +91,18 @@ class BxTimelineModule extends BxDolModule
             return;
         }
 
+        $aPhotos = $this->_oDb->getPhotos($iPostId);
+		if(is_array($aPhotos) && !empty($aPhotos)) {
+			bx_import('BxDolStorage');
+			$oStorage = BxDolStorage::getObjectInstance($this->_oConfig->getObject('storage'));
+
+			foreach($aPhotos as $aPhoto)
+				$oStorage->deleteFile($aPhoto['id']);
+
+			$this->_oDb->deletePhotos($iPostId);
+		}
+			
+
 		//--- Event -> Delete for Alerts Engine ---//
         bx_import('BxDolAlerts');
         $oAlert = new BxDolAlerts($this->_oConfig->getSystemName('alert'), 'delete', $iPostId, $this->getUserId());
@@ -107,7 +119,7 @@ class BxTimelineModule extends BxDolModule
         $this->_iOwnerId = bx_process_input(bx_get('owner_id'), BX_DATA_INT);
         $aEvent = $this->_oDb->getEvents(array('type' => 'id', 'object_id' => bx_process_input(bx_get('post_id'), BX_DATA_INT)));
 
-        $this->_echoResultJson(array('item' => $this->_oTemplate->getCommon($aEvent)));
+        $this->_echoResultJson(array('item' => $this->_oTemplate->getCommon($aEvent, array('type' => 'owner', 'owner_id' => $this->_iOwnerId))));
     }
 
 	function actionGetPosts()
@@ -115,7 +127,16 @@ class BxTimelineModule extends BxDolModule
         $this->_oConfig->setJsMode(true);
 
 		list($iStart, $iPerPage, $sFilter, $iTimeline, $aModules) = $this->_prepareParams();
-		list($sItems, $sLoadMore) = $this->_oTemplate->getPosts($this->_iOwnerId, 'desc', $iStart, $iPerPage, $sFilter, $iTimeline, $aModules);
+		list($sItems, $sLoadMore) = $this->_oTemplate->getPosts(array(
+			'type' => 'owner',
+			'owner_id' => $this->_iOwnerId, 
+			'order' => 'desc', 
+			'start' => $iStart, 
+			'per_page' => $iPerPage, 
+			'filter' => $sFilter, 
+			'timeline' => $iTimeline, 
+			'modules' => $aModules
+		));
 
 		$this->_echoResultJson(array('items' => $sItems, 'load_more' => $sLoadMore));
     }
@@ -149,8 +170,10 @@ class BxTimelineModule extends BxDolModule
     {
     	$this->_iOwnerId = bx_process_input(bx_get('owner_id'), BX_DATA_INT);
 
+    	$sSystem = bx_process_input(bx_get('system'), BX_DATA_TEXT);
     	$iId = bx_process_input(bx_get('id'), BX_DATA_INT);
-    	$aComments = $this->_oTemplate->getComments($iId);
+
+    	$aComments = $this->_oTemplate->getComments($sSystem, $iId);
 
     	$this->_echoResultJson($aComments);
     }
@@ -215,10 +238,9 @@ class BxTimelineModule extends BxDolModule
         $aMenu = array(
 			array('id' => 'timeline-ptype-text', 'name' => 'timeline-ptype-text', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changePostType(this, 'text')", 'target' => '_self', 'title' => _t('_bx_timeline_menu_item_write')),
 			array('id' => 'timeline-ptype-link', 'name' => 'timeline-ptype-link', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changePostType(this, 'link')", 'target' => '_self', 'title' => _t('_bx_timeline_menu_item_share_link')),
+			array('id' => 'timeline-ptype-photo', 'name' => 'timeline-ptype-photo', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changePostType(this, 'photo')", 'target' => '_self', 'title' => _t('_bx_timeline_menu_item_add_photo'))
         );
 
-        if($this->_oDb->isModule('photos'))
-            $aMenu[] = array('id' => 'timeline-ptype-photo', 'name' => 'timeline-ptype-photo', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changePostType(this, 'photo')", 'target' => '_self', 'title' => _t('_bx_timeline_menu_item_add_photo'));
         if($this->_oDb->isModule('sounds'))
             $aMenu[] = array('id' => 'timeline-ptype-sound', 'name' => 'timeline-ptype-sound', 'class' => '', 'link' => 'javascript:void(0)', 'onclick' => "javascript:" . $sJsObject . ".changePostType(this, 'sound')", 'target' => '_self', 'title' => _t('_bx_timeline_menu_item_add_music'));
         if($this->_oDb->isModule('videos'))
@@ -294,11 +316,7 @@ class BxTimelineModule extends BxDolModule
 			));
 
 			if($iId != 0) {
-				//--- Event -> Post for Alerts Engine ---//
-                bx_import('BxDolAlerts');
-                $oAlert = new BxDolAlerts($this->_oConfig->getSystemName('alert'), 'post', $iId, $iUserId);
-                $oAlert->alert();
-                //--- Event -> Post for Alerts Engine ---//
+				$this->_onPost($iId, $iUserId);
 
                 return array('id' => $iId);
 			}
@@ -351,11 +369,104 @@ class BxTimelineModule extends BxDolModule
 			));
 
 			if($iId != 0) {
-				//--- Event -> Post for Alerts Engine ---//
-                bx_import('BxDolAlerts');
-                $oAlert = new BxDolAlerts($this->_oConfig->getSystemName('alert'), 'post', $iId, $iUserId);
-                $oAlert->alert();
-                //--- Event -> Post for Alerts Engine ---//
+				$this->_onPost($iId, $iUserId);
+
+                return array('id' => $iId);
+			}
+
+			return array('msg' => _t('_bx_timeline_txt_err_cannot_perform_action'));
+        }
+
+        return array('form' => $oForm->getCode(), 'form_id' => $oForm->id);
+    }
+
+    public function getFormPhoto()
+    {
+    	$aFormNested = array(
+			'inputs' => array(
+		    	'file_title' => array(
+		        	'type' => 'text',
+		            'name' => 'file_title[]',
+		            'value' => '{file_title}',
+		            'caption' => _t('_bx_timeline_form_photo_input_title'),
+		            'required' => true,
+		            'checker' => array(
+		            	'func' => 'length',
+		                'params' => array(1, 150),
+		                'error' => _t('_bx_timeline_form_photo_input_err_title')
+					),
+					'db' => array (
+ 						'pass' => 'Xss',
+ 					),
+				),
+
+				'file_text' => array(
+		        	'type' => 'textarea',
+		            'name' => 'file_text[]',
+		            'caption' => _t('_bx_timeline_form_photo_input_description'),
+		            'required' => true,
+		            'checker' => array(
+		            	'func' => 'length',
+		                'params' => array(10, 5000),
+		                'error' => _t('_bx_timeline_form_photo_input_err_description')
+					),
+					'db' => array (
+ 						'pass' => 'Xss',
+ 					),
+				),
+			),
+		);
+
+    	bx_import('BxDolForm');
+        $oForm = BxDolForm::getObjectInstance('mod_tml_photo', 'mod_tml_photo_add');
+        $oForm->aFormAttrs['action'] = BX_DOL_URL_ROOT . $this->_oConfig->getBaseUri() . 'post/';
+        $oForm->aInputs['owner_id']['value'] = $this->_iOwnerId;
+
+	    bx_import('BxDolFormNested');
+	    $oFormNested = new BxDolFormNested('content', $aFormNested, 'do_submit');
+
+        $oForm->aInputs['content']['storage_object'] = $this->_oConfig->getObject('storage');
+        $oForm->aInputs['content']['images_transcoder'] = $this->_oConfig->getObject('transcoder_preview');
+        $oForm->aInputs['content']['uploaders'] = $this->_oConfig->getUploaders('image');
+        $oForm->aInputs['content']['multiple'] = false;
+        $oForm->aInputs['content']['ghost_template'] = $oFormNested;
+
+        $oForm->initChecker();
+        if($oForm->isSubmittedAndValid()) {
+        	$iUserId = $this->getUserId();
+        	list($sUserName) = $this->getUserInfo($iUserId);
+
+        	$sType = $oForm->getCleanValue('type');
+        	$sType = $this->_oConfig->getPrefix('common_post') . $sType;
+        	BxDolForm::setSubmittedValue('type', $sType, $oForm->aFormAttrs['method']);
+
+        	$aPhIds = $oForm->getCleanValue('content');
+        	BxDolForm::setSubmittedValue('content', serialize(array()), $oForm->aFormAttrs['method']);
+
+        	bx_import('BxDolPrivacy');
+        	$iId = $oForm->insert(array(
+        		'object_id' => $iUserId,
+        		'object_privacy_view' => BX_DOL_PG_ALL,
+				'title' => bx_process_input($sUserName . ' ' . _t('_bx_timeline_txt_added_photo')),
+				'description' => '',
+        		'date' => time()
+			));
+
+			if($iId != 0) {
+				$iPhIds = count($aPhIds);
+				if($iPhIds > 0) {
+					$aPhTitles = $oForm->getCleanValue('file_title');
+					$aPhTexts = $oForm->getCleanValue('file_text');
+
+					bx_import('BxDolStorage');
+					$oStorage = BxDolStorage::getObjectInstance($this->_oConfig->getObject('storage'));
+
+					for($i = 0; $i < $iPhIds; $i++) 
+						if($this->_oDb->savePhoto($iId, $aPhIds[$i], $aPhTitles[$i], $aPhTexts[$i]))
+							$oStorage->afterUploadCleanup($aPhIds[$i], $iUserId);
+				}
+
+				$this->_onPost($iId, $iUserId);
 
                 return array('id' => $iId);
 			}
@@ -366,10 +477,10 @@ class BxTimelineModule extends BxDolModule
         return array('form' => $oForm->getCode(), 'form_id' => $oForm->id);
     }
 
-    public function getCmtsObject($iId, $bPerform = false)
+    public function getCmtsObject($sSystem, $iId)
     {
     	bx_import('BxDolCmts');
-        $oCmts = BxDolCmts::getObjectInstance($this->_oConfig->getSystemName('comment'), $iId);
+        $oCmts = BxDolCmts::getObjectInstance($sSystem, $iId);
 		if(!$oCmts->isEnabled())
 			return false;
 
@@ -482,6 +593,15 @@ class BxTimelineModule extends BxDolModule
         BxDolAlerts::cache();
     }
 
+    protected function _onPost($iId, $iUserId)
+    {
+    	//--- Event -> Post for Alerts Engine ---//
+		bx_import('BxDolAlerts');
+        $oAlert = new BxDolAlerts($this->_oConfig->getSystemName('alert'), 'post', $iId, $iUserId);
+        $oAlert->alert();
+        //--- Event -> Post for Alerts Engine ---//
+    }
+
 	protected function _echoResultJson($a, $isAutoWrapForFormFileSubmit = false) {
 
         header('Content-type: text/html; charset=utf-8');    

+ 157 - 73
modules/boonex/timeline/classes/BxTimelineTemplate.php

@@ -55,6 +55,7 @@ class BxTimelineTemplate extends BxDolModuleTemplate
 
     	$aFormText = $oModule->getFormText();
     	$aFormLink = $oModule->getFormLink();
+    	$aFormPhoto = $oModule->getFormPhoto();
 
     	$this->addCss('post.css');
         $this->addJs(array('jquery.form.js', 'main.js', 'post.js'));
@@ -64,11 +65,9 @@ class BxTimelineTemplate extends BxDolModuleTemplate
             	'owner_id' => $iOwnerId 
         	)),
             'text_form' => $aFormText['form'],
-        	'text_form_id' => $aFormText['form_id'],
             'link_form' => $aFormLink['form'],
-        	'link_form_id' => $aFormLink['form_id'],
-
-            'photo_form' => '',
+            'photo_form' => $aFormPhoto['form'],
+        	
         	'music_form' => '',
             'video_form' => '',
         ));        
@@ -85,10 +84,19 @@ class BxTimelineTemplate extends BxDolModuleTemplate
         if(empty($sFilter))
             $sFilter = BX_TIMELINE_FILTER_ALL;
 
-		list($sContent, $sLoadMore) = $this->getPosts($iOwnerId, 'desc', $iStart, $iPerPage, $sFilter, $iTimeline, $aModules);
+		list($sContent, $sLoadMore) = $this->getPosts(array(
+			'type' => 'owner',
+			'owner_id' => $iOwnerId, 
+		 	'order' => 'desc', 
+			'start' => $iStart, 
+			'per_page' => $iPerPage, 
+			'filter' => $sFilter, 
+			'timeline' => $iTimeline, 
+			'modules' => $aModules
+		));
 
     	$this->addCss(array('plugins/jquery/themes/|jquery-ui.css', 'view.css', 'view-media-tablet.css', 'view-media-desktop.css'));
-        $this->addJs(array('jquery.ui.all.min.js', 'plugins/|masonry.pkgd.min.js', 'common_anim.js', 'main.js', 'view.js'));
+        $this->addJs(array('jquery.ui.all.min.js', 'jquery.resize.js', 'plugins/|masonry.pkgd.min.js', 'common_anim.js', 'main.js', 'view.js'));
 
         $oModule = $this->getModule();
         bx_import('Cmts', $oModule->_aModule);
@@ -110,36 +118,28 @@ class BxTimelineTemplate extends BxDolModuleTemplate
         ));
     }
 
-	public function getPosts($iOwnerId, $sOrder, $iStart, $iPerPage, $sFilter, $iTimeline, $aModules)
+	public function getPosts($aParams)
     {
-        $iStartEv = $iStart;
-        $iPerPageEv = $iPerPage;
+    	$iStart = $aParams['start'];
+    	$iPerPage = $aParams['per_page'];
+
+        $aParamsDb = $aParams;
 
         //--- Check for Previous
         $bPrevious = false;
         if($iStart - 1 >= 0) {
-            $iStartEv -= 1;
-            $iPerPageEv += 1;
+            $aParamsDb['start'] -= 1;
+            $aParamsDb['per_page'] += 1;
             $bPrevious = true;
         }
 
         //--- Check for Next
-        $iPerPageEv += 1;
-        $aEvents = $this->_oDb->getEvents(array(
-        	'type' => 'owner', 
-        	'owner_id' => $iOwnerId, 
-        	'order' => $sOrder, 
-        	'start' => $iStartEv, 
-        	'count' => $iPerPageEv, 
-        	'filter' => $sFilter, 
-        	'timeline' => $iTimeline,
-        	'modules' => $aModules
-        ));
+        $aParamsDb['per_page'] += 1;
+        $aEvents = $this->_oDb->getEvents($aParamsDb);
 
         //--- Check for Previous
-        if($bPrevious) {
+        if($bPrevious)
             $aEvent = array_shift($aEvents);
-        }
 
         //--- Check for Next
         $bNext = false;
@@ -152,7 +152,7 @@ class BxTimelineTemplate extends BxDolModuleTemplate
         $sContent = $this->getEmpty($iEvents <= 0);
 
         foreach($aEvents as $aEvent) {
-            $aEvent['content'] = !empty($aEvent['action']) ? $this->getSystem($aEvent) : $this->getCommon($aEvent);
+            $aEvent['content'] = !empty($aEvent['action']) ? $this->getSystem($aEvent, $aParams) : $this->getCommon($aEvent, $aParams);
             if(empty($aEvent['content']))
                 continue;
 
@@ -209,29 +209,25 @@ class BxTimelineTemplate extends BxDolModuleTemplate
         return $this->parseHtmlByName('load_more.html', $aTmplVars);
     }
 
-    public function getComments($iId)
+    public function getComments($sSystem, $iId)
     {
     	$oModule = $this->getModule();
     	$sStylePrefix = $this->_oConfig->getPrefix('style');
 
-    	$oCmts = $oModule->getCmtsObject($iId);
+    	$oCmts = $oModule->getCmtsObject($sSystem, $iId);
     	if($oCmts === false)
 			return array();
 
-		$sName = $sStylePrefix . '-comments-' . $iId;
-		$sTitle = _t('_bx_timeline_page_block_title_comments');
 		$sContent = $this->parseHtmlByName('comments.html', array(
         	'style_prefix' => $sStylePrefix,
-        	'content' => $oCmts->getCommentsBlock()
+			'id' => $iId,
+        	'content' => $oCmts->getCommentsBlock(0, 0, false)
         ));
 
-		bx_import('BxTemplFunctions');
-        $sContent = BxTemplFunctions::getInstance()->popupBox($sName, $sTitle, $sContent);
-
-        return array('popup' => $sContent);
+        return array('content' => $sContent);
     }
 
-	function getSystem($aEvent)
+	function getSystem($aEvent, $aBrowseParams)
     {
         $aResult = $this->_getSystemData($aEvent);
 		if(empty($aResult) || empty($aResult['owner_id']) || empty($aResult['content']))
@@ -251,32 +247,67 @@ class BxTimelineTemplate extends BxDolModuleTemplate
 		$aEvent['comments'] = $aResult['comments'];
 
 		$sType = !empty($aResult['content_type']) ? $aResult['content_type'] : BX_TIMELINE_PARSE_TYPE_DEFAULT;
-        return $this->_getPost($sType, $aEvent);
+        return $this->_getPost($sType, $aEvent, $aBrowseParams);
     }
 
-    public function getCommon($aEvent)
+    public function getCommon($aEvent, $aBrowseParams)
     {
     	$sPrefix = $this->_oConfig->getPrefix('common_post');
         if(strpos($aEvent['type'], $sPrefix) !== 0)
             return '';
 
+		$oModule = $this->getModule();
+
+		$sType = str_replace($sPrefix, '', $aEvent['type']);
 		$aEvent['object_owner_id'] = $aEvent['object_id'];
 
-		if(!empty($aEvent['content']))
-			$aEvent['content'] = unserialize($aEvent['content']);
+		switch($sType) {
+			case BX_TIMELINE_PARSE_TYPE_TEXT:
+			case BX_TIMELINE_PARSE_TYPE_LINK:
+				if(!empty($aEvent['content']))
+					$aEvent['content'] = unserialize($aEvent['content']);
+				break;
+
+			case BX_TIMELINE_PARSE_TYPE_PHOTO:
+				$aPhotos = $this->_oDb->getPhotos($aEvent['id']);
+				if(!is_array($aPhotos) || empty($aPhotos))
+					break;
+
+	        	$aEvent['content'] = array(
+	        		'title' => '', 
+	        		'text' => ''
+	        	);
+
+	        	$aFirst = current($aPhotos);
+	        	if($aFirst !== false) {
+	        		$aEvent['content']['title'] = isset($aFirst['title']) ? $aFirst['title'] : '';
+	        		$aEvent['content']['text'] = isset($aFirst['text']) ? $aFirst['text'] : '';
+	        	}
+
+				bx_import('BxDolImageTranscoder');
+	        	$oTranscoder = BxDolImageTranscoder::getObjectInstance($this->_oConfig->getObject('transcoder_preview'));
+
+				foreach($aPhotos as $aPhoto)
+					$aEvent['content']['images'][] = array(
+						'src' => $oTranscoder->getImageUrl($aPhoto['id']),
+						'title' => isset($aPhoto['title']) ? $aPhoto['title'] : '' 
+					); 
+
+				break;
+		}
 
-		$oModule = $this->getModule();
-        $oCmts = $oModule->getCmtsObject($aEvent['id']);
-        if($oCmts !== false)
+		$sSystem = $this->_oConfig->getSystemName('comment');
+        if($oModule->getCmtsObject($sSystem, $aEvent['id']) !== false)
         	$aEvent['comments'] = array(
-				'count' => $aEvent['comments'],
-				'onclick' => 'javascript:' . $this->_oConfig->getJsObject('view') . '.showComments(this, ' . $aEvent['id'] . ')'
+        		'system' => $sSystem,
+        		'object_id' => $aEvent['id'],
+				'count' => $aEvent['comments']
 			);
 
-		return $this->_getPost(str_replace($sPrefix, '', $aEvent['type']), $aEvent);
+		return $this->_getPost($sType, $aEvent, $aBrowseParams);
     }
 
-	protected function _getPost($sType, $aEvent)
+	protected function _getPost($sType, $aEvent, $aBrowseParams)
     {
 		$oModule = $this->getModule();
 		$sStylePrefix = $this->_oConfig->getPrefix('style');
@@ -286,7 +317,10 @@ class BxTimelineTemplate extends BxDolModuleTemplate
         $bAuthorIcon = !empty($sAuthorIcon);
 
         $aTmplVarsMenu = $this->_getTmplVarsItemMenu($aEvent);
-        $aTmplVarsTimelineOwner = $this->_getTmplVarsTimelineOwner($aEvent);
+
+        $aTmplVarsTimelineOwner = array();
+        if($aBrowseParams['type'] == 'friends')
+        	$aTmplVarsTimelineOwner = $this->_getTmplVarsTimelineOwner($aEvent);
 
         $aTmplVars = array (
         	'style_prefix' => $sStylePrefix,
@@ -376,21 +410,27 @@ class BxTimelineTemplate extends BxDolModuleTemplate
 
 	protected function _getTmplVarsComments($aComments)
     {
-    	$iCount = isset($aComments['count']) ? (int)$aComments['count'] : 0;
-		$sViewUrl = isset($aComments['url']) ? $aComments['url'] : 'javascript:void(0)';
+    	$sStylePrefix = $this->_oConfig->getPrefix('style');
 
-		$aTmplVarsAttrs = array();
-		if(isset($aComments['onclick']))
-			$aTmplVarsAttrs[] = array('key' => 'onclick', 'value' => $aComments['onclick']);
+    	$sSystem = isset($aComments['system']) ? $aComments['system'] : '';
+    	$iObjectId = isset($aComments['object_id']) ? (int)$aComments['object_id'] : 0;
+    	$iCount = isset($aComments['count']) ? (int)$aComments['count'] : 0;
+    	if($sSystem == '' || $iObjectId == 0 || ($iCount == 0 && !isLogged()))
+    		return array();
 
 		return array(
     		'bx_if:show_comments' => array(
     			'condition' => true,
     			'content' => array(
-					'href' => $sViewUrl,
-					'title' => _t('_bx_timeline_txt_view_comments'),
-					'bx_repeat:attrs' => $aTmplVarsAttrs,
-					'content' => _t('_bx_timeline_txt_n_comments', $iCount)
+					'style_prefix' => $sStylePrefix,
+					'link' => $this->parseHtmlByName('bx_a.html', array(
+						'href' => 'javascript:void(0)',
+						'title' => _t($iCount > 0 ? '_bx_timeline_txt_view_comments' : '_bx_timeline_txt_add_comment'),
+						'bx_repeat:attrs' => array(
+							array('key' => 'onclick', 'value' => "javascript:" . $this->_oConfig->getJsObject('view') . ".showComments(this, '" . $sSystem . "', " . $iObjectId . ")")
+						),
+						'content' => $iCount > 0 ? _t('_bx_timeline_txt_n_comments', $iCount) : _t('_bx_timeline_txt_add_comment')
+					))
 				)
 			)
 		);
@@ -402,9 +442,18 @@ class BxTimelineTemplate extends BxDolModuleTemplate
 		$sJsObject = $this->_oConfig->getJsObject('view');
 
 		$sUrl = isset($aContent['url']) ? $aContent['url'] : '';
-		$sTile = isset($aContent['title']) ? $aContent['title'] : '';
-
-    	$sText = isset($aContent['text']) ? $aContent['text'] : '';
+		$sTitle = isset($aContent['title']) ? bx_process_input(strip_tags($aContent['title'])) : '';
+		if(!empty($sUrl) && !empty($sTitle))
+			$sTitle = $this->parseHtmlByName('bx_a.html', array(
+				'href' => $sUrl,
+				'title' => $sTitle,
+				'bx_repeat:attrs' => array(
+					array('key' => 'class', 'value' => $sStylePrefix . '-title')
+				),
+				'content' => $sTitle
+			));
+
+    	$sText = isset($aContent['text']) ? bx_process_input(strip_tags($aContent['text'])) : '';
 		$sTextMore = '';
 
 		$iMaxLength = $this->_oConfig->getCharsDisplayMax();
@@ -419,16 +468,15 @@ class BxTimelineTemplate extends BxDolModuleTemplate
 		$sTextMore = $this->_prepareTextForOutput($sTextMore);
 
 		return array(
-			'bx_if:show_image' => array(
+			'bx_if:show_images' => array(
 				'condition' => false,
             	'content' => array()
 			),
         	'bx_if:show_title' => array(
-		    	'condition' => !empty($sTile),
+		    	'condition' => !empty($sTitle),
             	'content' => array(
 					'style_prefix' => $sStylePrefix,
-					'item_url' => $sUrl,
-					'item_title' => $sTile,
+					'title' => $sTitle,
 				)
 			),
 			'bx_if:show_content' => array(
@@ -454,24 +502,60 @@ class BxTimelineTemplate extends BxDolModuleTemplate
     	return $this->_getTmplVarsContentText($aContent);
     }
 
-    protected function _getTmplVarsContentImage($aContent)
+    protected function _getTmplVarsContentPhoto($aContent)
     {
     	$aTmplVarsContent = $this->_getTmplVarsContentText($aContent);
-    	if(!isset($aContent['image']) || empty($aContent['image']))
+    	if(!isset($aContent['images']) || empty($aContent['images']))
     		return $aTmplVarsContent;
 
 		$sStylePrefix = $this->_oConfig->getPrefix('style');
-
-		$sImageUrl = $aContent['image'];
-		$sImageTitle = isset($aContent['title']) ? $aContent['title'] : '';
+		$sTitleDefault = isset($aContent['title']) && !empty($aContent['title']) ? $aContent['title'] : "";
+
+		$aTmplVarsImages = array();
+		foreach($aContent['images'] as $aImage) {
+			$sTitle = isset($aImage['title']) && !empty($aImage['title']) ? $aImage['title'] : $sTitleDefault;
+
+			$sImage = '';
+			if(isset($aImage['src']) && !empty($aImage['src']))
+				$sImage = $this->parseHtmlByName('bx_img.html', array(
+					'src' => $aImage['src'],
+					'bx_repeat:attrs' => array(
+						array('key' => 'class', 'value' => $sStylePrefix . '-item-image'),
+						array('key' => 'title', 'value' => $sTitle)
+					)
+				));
+
+			if(!empty($sImage) && (isset($aImage['url']) || isset($aImage['onclick']))) {
+				$aAttrs = array();
+				if(isset($aImage['onclick']))
+					$aAttrs[] = array('key' => 'onclick', 'value' => $aImage['onclick']);
+
+				$sImage = $this->parseHtmlByName('bx_a.html', array(
+					'href' => isset($aImage['url']) ? $aImage['url'] : 'javascript:void(0)',
+					'title' => $sTitle,
+					'bx_repeat:attrs' => $aAttrs,
+					'content' => $sImage
+				));
+			}
+
+			$aTmplVarsImages[] = array(
+				'style_prefix' => $sStylePrefix,
+				'image' => $sImage
+			);
+		}
+			/*
+				$aTmplVarsImages[] = array(
+					'style_prefix' => $sStylePrefix,
+					'src' => $aImage['src'],
+					'title' => isset($aImage['title']) && !empty($aImage['title']) ? $aImage['title'] : $sTitleDefault
+				);
+			*/
 
     	return array_merge($aTmplVarsContent, array(
-    		'bx_if:show_image' => array(
-				'condition' => true,
+    		'bx_if:show_images' => array(
+				'condition' => !empty($aTmplVarsImages),
             	'content' => array(
-    				'style_prefix' => $sStylePrefix,
-    				'image_url' => $sImageUrl,
-    				'image_title' => $sImageTitle,
+    				'bx_repeat:images' => $aTmplVarsImages
     			)
 			)
     	));

+ 18 - 1
modules/boonex/timeline/install/langs/en.xml

@@ -32,6 +32,22 @@
 	<string name="_bx_timeline_form_link_input_sys_do_submit"><![CDATA[Post]]></string>
 	<string name="_bx_timeline_form_link_input_do_submit"><![CDATA[Post]]></string>
 
+	<string name="_bx_timeline_form_photo"><![CDATA[Post an Image]]></string>
+	<string name="_bx_timeline_form_photo_display_add"><![CDATA[Post an Image]]></string>
+	<string name="_bx_timeline_form_photo_input_sys_owner_id"><![CDATA[Owner ID]]></string>
+	<string name="_bx_timeline_form_photo_input_sys_action"><![CDATA[Action]]></string>
+	<string name="_bx_timeline_form_photo_input_sys_type"><![CDATA[Type]]></string>
+	<string name="_bx_timeline_form_photo_input_sys_content"><![CDATA[Content]]></string>
+	<string name="_bx_timeline_form_photo_input_content"><![CDATA[]]></string>
+	<string name="_bx_timeline_form_photo_input_sys_do_submit"><![CDATA[Post]]></string>
+	<string name="_bx_timeline_form_photo_input_do_submit"><![CDATA[Post]]></string>
+	<string name="_bx_timeline_form_photo_input_title"><![CDATA[Title]]></string>
+	<string name="_bx_timeline_form_photo_input_err_title"><![CDATA[Title is required.]]></string>
+	<string name="_bx_timeline_form_photo_input_title"><![CDATA[Title]]></string>
+	<string name="_bx_timeline_form_photo_input_err_title"><![CDATA[Title is required.]]></string>
+	<string name="_bx_timeline_form_photo_input_description"><![CDATA[Description]]></string>
+	<string name="_bx_timeline_form_photo_input_err_description"><![CDATA[Description is required.]]></string>
+
 	<string name="_bx_timeline_acl_action_post"><![CDATA[Post]]></string>
 	<string name="_bx_timeline_acl_action_delete"><![CDATA[Delete]]></string>
 	<string name="_bx_timeline_acl_action_comment"><![CDATA[Comment]]></string>
@@ -42,7 +58,6 @@
 	<string name="_bx_timeline_page_block_title_view"><![CDATA[View Timeline]]></string>
 	<string name="_bx_timeline_page_block_title_post_profile"><![CDATA[Post to Timeline]]></string>
 	<string name="_bx_timeline_page_block_title_view_profile"><![CDATA[Timeline]]></string>
-	<string name="_bx_timeline_page_block_title_comments"><![CDATA[Comments]]></string>
 
 	<!--
     <string name="_bx_timeline_menu_set_title_XXX"><![CDATA[Notes Submenu]]></string>
@@ -75,9 +90,11 @@
 	<string name="_bx_timeline_txt_load_more"><![CDATA[Load More]]></string>
 	<string name="_bx_timeline_txt_wrote"><![CDATA[wrote]]></string>
 	<string name="_bx_timeline_txt_shared_link"><![CDATA[shared a link]]></string>
+	<string name="_bx_timeline_txt_added_photo"><![CDATA[added a photo]]></string>
 	<string name="_bx_timeline_txt_date_from"><![CDATA[today]]></string>
 	<string name="_bx_timeline_txt_date_to"><![CDATA[olden days]]></string>
 	<string name="_bx_timeline_txt_view_comments"><![CDATA[View comments]]></string>
+	<string name="_bx_timeline_txt_add_comment"><![CDATA[Add comment]]></string>
 	<string name="_bx_timeline_txt_n_comments"><![CDATA[{0} comments]]></string>
 
 </resources>

+ 6 - 0
modules/boonex/timeline/install/sql/disable.sql

@@ -11,6 +11,12 @@ DELETE FROM `sys_options_categories` WHERE `type_id` = @iTypeId;
 DELETE FROM `sys_options_types` WHERE `id` = @iTypeId;
 
 
+-- UPLOADERS
+DELETE FROM `sys_objects_uploader` WHERE `object` = 'bx_timeline_simple';
+DEKETE FROM `sys_objects_storage` WHERE `object` IN ('bx_timeline_photos', 'bx_timeline_photos_preview');
+DELETE FROM `sys_objects_transcoder_images` WHERE `object` IN ('bx_timeline_photos_preview');
+DELETE FROM `sys_transcoder_images_filters` WHERE `transcoder_object` IN ('bx_timeline_photos_preview');
+
 -- ACL
 DELETE `sys_acl_actions`, `sys_acl_matrix` FROM `sys_acl_actions`, `sys_acl_matrix` WHERE `sys_acl_matrix`.`IDAction` = `sys_acl_actions`.`ID` AND `sys_acl_actions`.`Module` = 'bx_timeline';
 DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_timeline';

+ 15 - 0
modules/boonex/timeline/install/sql/enable.sql

@@ -33,6 +33,21 @@ INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `c
 ('bx_timeline_chars_display_max', '300', @iCategId, 'Max number of displayed character in text post', 'digit', '', '', '', 7);
 
 
+-- UPLOADERS
+INSERT INTO `sys_objects_uploader` (`object`, `active`, `override_class_name`, `override_class_file`) VALUES
+('bx_timeline_simple', 1, 'BxTemplUploaderSimple', '');
+
+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
+('bx_timeline_photos', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0),
+('bx_timeline_photos_preview', 'Local', '', 360, 2592000, 3, 'bx_timeline_photos_preview', 'allow-deny', 'jpg,jpeg,jpe,gif,png', '', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO `sys_objects_transcoder_images` (`object`, `storage_object`, `source_type`, `source_params`, `private`, `atime_tracking`, `atime_pruning`, `ts`) VALUES
+('bx_timeline_photos_preview', 'bx_timeline_photos_preview', 'Storage', 'a:1:{s:6:"object";s:18:"bx_timeline_photos";}', 'no', '1', '2592000', '0');
+
+INSERT INTO `sys_transcoder_images_filters` (`transcoder_object`, `filter`, `filter_params`, `order`) VALUES
+('bx_timeline_photos_preview', 'Resize', 'a:4:{s:1:"w";s:3:"319";s:1:"h";s:3:"319";s:13:"square_resize";s:1:"1";s:10:"force_type";s:3:"jpg";}', '0');
+
+
 -- ACL
 INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
 ('bx_timeline', 'post', NULL, '_bx_timeline_acl_action_post', '', 1, 1);

+ 65 - 1
modules/boonex/timeline/install/sql/install.sql

@@ -34,7 +34,50 @@ INSERT INTO `bx_timeline_handlers`(`type`, `alert_unit`, `alert_action`, `conten
 ('insert', 'timeline_common_sounds', '', ''),
 ('insert', 'timeline_common_videos', '', '');
 
--- TABLE: COMMENTS
+-- TABLES: UPLOADERS
+CREATE TABLE IF NOT EXISTS `bx_timeline_photos` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(255) NOT NULL,
+  `path` varchar(255) NOT NULL,
+  `file_name` varchar(255) NOT NULL,
+  `mime_type` varchar(128) NOT NULL,
+  `ext` varchar(32) NOT NULL,
+  `size` int(11) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_timeline_photos_preview` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `profile_id` int(10) unsigned NOT NULL,
+  `remote_id` varchar(255) NOT NULL,
+  `path` varchar(255) NOT NULL,
+  `file_name` varchar(255) NOT NULL,
+  `mime_type` varchar(128) NOT NULL,
+  `ext` varchar(32) NOT NULL,
+  `size` int(11) NOT NULL,
+  `added` int(11) NOT NULL,
+  `modified` int(11) NOT NULL,
+  `private` int(11) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `remote_id` (`remote_id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_timeline_photos2events` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `event_id` int(11) NOT NULL DEFAULT '0',
+  `photo_id` int(11) NOT NULL DEFAULT '0',
+  `title` varchar(255) collate utf8_unicode_ci NOT NULL,
+  `text` text collate utf8_unicode_ci NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `photo` (`event_id`, `photo_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+-- TABLES: COMMENTS
 CREATE TABLE IF NOT EXISTS `bx_timeline_comments` (
   `cmt_id` int(11) NOT NULL AUTO_INCREMENT,
   `cmt_parent_id` int(11) NOT NULL DEFAULT '0',
@@ -104,6 +147,27 @@ INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_fo
 ('mod_tml_link_add', 'content', 2147483647, 1, 4),
 ('mod_tml_link_add', 'do_submit', 2147483647, 1, 5);
 
+-- Forms -> Photo
+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
+('mod_tml_photo', @sName, '_bx_timeline_form_photo', '', '', 'do_submit', 'bx_timeline_events', 'id', '', '', '', 0, 1, '', '');
+
+INSERT INTO `sys_form_displays` (`display_name`, `module`, `object`, `title`, `view_mode`) VALUES
+('mod_tml_photo_add', @sName, 'mod_tml_photo', '_bx_timeline_form_photo_display_add', 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
+('mod_tml_photo', @sName, 'type', 'photo', '', 0, 'hidden', '_bx_timeline_form_photo_input_sys_type', '', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('mod_tml_photo', @sName, 'action', '', '', 0, 'hidden', '_bx_timeline_form_photo_input_sys_action', '', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('mod_tml_photo', @sName, 'owner_id', '0', '', 0, 'hidden', '_bx_timeline_form_photo_input_sys_owner_id', '', '', 0, 0, 0, '', '', '', '', '', '', 'Int', '', 0, 0),
+('mod_tml_photo', @sName, 'content', '', '', 0, 'files', '_bx_timeline_form_photo_input_sys_content', '_bx_timeline_form_photo_input_content', '', 0, 0, 0, '', '', '', '', '', '', 'Xss', '', 0, 0),
+('mod_tml_photo', @sName, 'do_submit', '_bx_timeline_form_photo_input_do_submit', '', 0, 'submit', '_bx_timeline_form_photo_input_sys_do_submit', '', '', 0, 0, 0, '', '', '', '', '', '', '', '', 0, 0);
+
+INSERT INTO `sys_form_display_inputs` (`display_name`, `input_name`, `visible_for_levels`, `active`, `order`) VALUES
+('mod_tml_photo_add', 'type', 2147483647, 1, 1),
+('mod_tml_photo_add', 'action', 2147483647, 1, 2),
+('mod_tml_photo_add', 'owner_id', 2147483647, 1, 3),
+('mod_tml_photo_add', 'content', 2147483647, 1, 4),
+('mod_tml_photo_add', 'do_submit', 2147483647, 1, 5);
+
 -- STUDIO PAGE & WIDGET
 INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
 (3, 'bx_timeline', '_bx_timeline', '_bx_timeline', 'bx_timeline@modules/boonex/timeline/|std-pi.png');

+ 5 - 0
modules/boonex/timeline/install/sql/uninstall.sql

@@ -2,6 +2,11 @@ SET @sName = 'bx_timeline';
 
 DROP TABLE IF EXISTS `bx_timeline_events`;
 DROP TABLE IF EXISTS `bx_timeline_handlers`;
+
+DROP TABLE IF EXISTS `bx_timeline_photos`;
+DROP TABLE IF EXISTS `bx_timeline_photos_preview`;
+DROP TABLE IF EXISTS `bx_timeline_photos2events`;
+
 DROP TABLE IF EXISTS `bx_timeline_comments`;
 DROP TABLE IF EXISTS `bx_timeline_comments_track`;
 

+ 4 - 1
modules/boonex/timeline/js/main.js

@@ -1,8 +1,11 @@
 function BxTimelineMain() {
+	this.sIdPost = '#bx-timeline-post';
+	
 	this.sIdView = '#bx-timeline';
 	this.sIdItem = '#bx-timeline-item-';
+	this.sIdComments = '#bx-timeline-comments-';
 
-	this.sClassMasonry = "bx-tl-masonry";
+	this.sClassMasonry = 'bx-tl-masonry';
 }
 
 BxTimelineMain.prototype.isMasonry = function() {

+ 9 - 1
modules/boonex/timeline/js/post.js

@@ -5,6 +5,14 @@ function BxTimelinePost(oOptions) {
     this._sAnimationEffect = oOptions.sAnimationEffect == undefined ? 'slide' : oOptions.sAnimationEffect;
     this._iAnimationSpeed = oOptions.iAnimationSpeed == undefined ? 'slow' : oOptions.iAnimationSpeed;
     this._oRequestParams = oOptions.oRequestParams == undefined ? {} : oOptions.oRequestParams;
+
+    var $this = this;
+    $(document).ready(function () {
+    	$($this.sIdPost + ' form').each(function() {
+    		var sId = $(this).attr('id');
+    		$this.initFormPost(sId);
+    	});
+	});
 }
 
 BxTimelinePost.prototype = new BxTimelineMain();
@@ -67,7 +75,7 @@ BxTimelinePost.prototype.changePostType = function(oLink, sType) {
     	return;
     }
 
-    if(sType == 'photo' || sType == 'music' || sType == 'video') {
+    if(sType == 'music' || sType == 'video') {
         jQuery.post (
             $this._sActionsUrl + 'get_' + sType + '_uploaders/' + this._iOwnerId,
             {},

+ 14 - 11
modules/boonex/timeline/js/view.js

@@ -9,6 +9,9 @@ function BxTimelineView(oOptions) {
     var $this = this;
     $(document).ready(function() {
     	$this.initMasonry();
+    	$('.bx-tl-item').resize(function() {
+    		$this.reloadMasonry();
+    	});
     	$('.bx-tl-item-image').load(function() {
     		$this.reloadMasonry();
     	});
@@ -103,15 +106,21 @@ BxTimelineView.prototype.showPostMenu = function(oLink) {
 	bx_menu_popup_inline('#' + sId, oLink);
 };
 
-BxTimelineView.prototype.showComments = function(oLink, iId) {
+BxTimelineView.prototype.showComments = function(oLink, sSystem, iId) {
 	var $this = this;
 
     var oData = this._getDefaultData();
     oData['id'] = iId;
+    oData['system'] = sSystem;
 
     if(oLink)
     	this.loadingInBlock(oLink, true);
 
+    if($(this.sIdComments + iId + ':hidden').length > 0) {
+    	$(oLink).next(this.sIdComments + iId + ':hidden').bx_anim('show', this._sAnimationEffect, this._iAnimationSpeed);
+    	return;
+    }
+
     jQuery.post (
         this._sActionsUrl + 'get_comments',
         oData,
@@ -119,18 +128,12 @@ BxTimelineView.prototype.showComments = function(oLink, iId) {
         	if(oLink)
         		$this.loadingInBlock(oLink, false);
 
-        	if(!oData.popup)
+        	if(!oData.content)
         		return;
 
-        	var oPopup = $(oData.popup); 
-
-	    	$('#' + oPopup.attr('id')).remove();
-	        oPopup.prependTo('body').dolPopup({
-	        	fog: {
-					color: '#fff',
-					opacity: .7
-	            }
-	        }).bxTime();
+        	$(oLink).bx_anim('hide', $this._sAnimationEffect, $this._iAnimationSpeed, function() {
+        		$(this).parents('.bx-tl-comments-link:first').after($(oData.content).hide()).next($this.sIdComments + iId + ':hidden').bxTime().bx_anim('show', $this._sAnimationEffect, $this._iAnimationSpeed);
+    		});
         },
         'json'
     );

+ 1 - 3
modules/boonex/timeline/template/comments.html

@@ -1,3 +1 @@
-<div class="__style_prefix__-comments">
-	<div class="__style_prefix__-comments-cnt bx-def-padding">__content__</div>
-</div>
+<div id="bx-timeline-comments-__id__" class="__style_prefix__-comments">__content__</div>

+ 1 - 6
modules/boonex/timeline/template/css/view.css

@@ -115,9 +115,4 @@ div.bx-tl-timeline .ui-slider-handle:hover {
 }
 
 /*--- Comments Styles ---*/
-div.bx-tl-comments {
-	margin: -20px;
-	max-height: 500px;
-	overflow: auto;
-}
-div.bx-tl-comments-cnt {}
+div.bx-tl-comments {}

+ 0 - 0
modules/boonex/timeline/template/image.html → modules/boonex/timeline/template/photo.html


+ 3 - 17
modules/boonex/timeline/template/post.html

@@ -1,22 +1,8 @@
-<div class="timeline-post bx-def-bc-margin">    
+<div id="bx-timeline-post" class="timeline-post bx-def-bc-margin">    
     <!-- Post Text -->
-    <div id="timeline-ptype-cnt-text" class="timeline-ptype-cnt timeline-text">
-    	__text_form__
-    	<script type="text/javascript">
-	    	$(document).ready(function () {
-	        	__js_object__.initFormPost('__text_form_id__');
-	    	});
-    	</script>
-    </div>
+    <div id="timeline-ptype-cnt-text" class="timeline-ptype-cnt timeline-text">__text_form__</div>
     <!-- Post Link -->
-    <div id="timeline-ptype-cnt-link" class="timeline-ptype-cnt timeline-link">
-		__link_form__
-    	<script type="text/javascript">
-	    	$(document).ready(function () {
-				__js_object__.initFormPost('__link_form_id__');
-	    	});
-    	</script>
-	</div>
+    <div id="timeline-ptype-cnt-link" class="timeline-ptype-cnt timeline-link">__link_form__</div>
     <!-- Post Photo -->
     <div id="timeline-ptype-cnt-photo" class="timeline-ptype-cnt timeline-photo">__photo_form__</div>
     <!-- Post Music -->

+ 10 - 13
modules/boonex/timeline/template/text.html

@@ -1,11 +1,11 @@
 <div id="bx-timeline-item-__post_id__" class="__style_prefix__-item bx-tl-grid-sizer">
 	<div class="__style_prefix__-item-ind bx-def-padding-sec">
 	    <div class="__style_prefix__-item-cnt bx-def-padding bx-def-round-corners bx-def-shadow">
-	    	<bx_if:show_image>
-	    		<div class="__style_prefix__-item-image bx-def-thumb bx-def-margin-bottom">
-	    			<img class="__style_prefix__-item-image" alt="__image_title__" src="__image_url__" />
-	    		</div>
-	    	</bx_if:show_image>
+	    	<bx_if:show_images>
+	    		<bx_repeat:images>
+		    		<div class="__style_prefix__-item-image bx-def-thumb bx-def-margin-bottom">__image__</div>
+		    	</bx_repeat:images>
+	    	</bx_if:show_images>
 	    	<div class="__style_prefix__-owner bx-def-icon-size-min-height"> 
 		       	<div class="__style_prefix__-owner-icon bx-def-icon bx-def-icon-size">
 					<bx_if:show_icon>
@@ -22,11 +22,7 @@
 			        	<a href="__owner_url__" class="__style_prefix__-timeline-owner-name">__owner_username__</a>
 			        </bx_if:show_timeline_owner>
 				</div>
-				<div class="__style_prefix__-date bx-def-font-small bx-def-font-grayed">__item_date__
-					<bx_if:show_comments>
-						<i class="sys-icon comments bx-def-padding-left bx-def-padding-thd-right"></i><bx_include_auto:bx_a.html />
-					</bx_if:show_comments>
-				</div>
+				<div class="__style_prefix__-date bx-def-font-small bx-def-font-grayed">__item_date__</div>
 				<bx_if:show_menu>
 					<div class="__style_prefix__-item-menu">
 						__menu__
@@ -39,9 +35,7 @@
 			   	</bx_if:show_menu>
 			</div>
 			<bx_if:show_title>
-				<div class="__style_prefix__-title bx-def-margin-sec-top bx-def-font-large">
-					<a href="__item_url__">__item_title__</a>
-				</div>
+				<div class="__style_prefix__-title bx-def-margin-sec-top bx-def-font-large">__title__</div>
 			</bx_if:show_title>
 			<bx_if:show_content>
 				<div class="__style_prefix__-content bx-def-margin-sec-top bx-def-font-grayed">
@@ -49,6 +43,9 @@
 				</div>
 			</bx_if:show_content>
 
+			<bx_if:show_comments>
+				<div class="__style_prefix__-comments-link bx-def-margin-sec-top">__link__</div>
+			</bx_if:show_comments>
 			<!--
 			<div class="__style_prefix__-comments bx-def-margin-sec-top bx-def-border bx-def-round-corners">
 				<i class="sys-icon comment-alt"></i>

+ 246 - 0
plugins/jquery/jquery.resize.js

@@ -0,0 +1,246 @@
+/*!
+ * jQuery resize event - v1.1 - 3/14/2010
+ * http://benalman.com/projects/jquery-resize-plugin/
+ * 
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery resize event
+//
+// *Version: 1.1, Last updated: 3/14/2010*
+// 
+// Project Home - http://benalman.com/projects/jquery-resize-plugin/
+// GitHub       - http://github.com/cowboy/jquery-resize/
+// Source       - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
+// (Minified)   - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
+// 
+// About: License
+// 
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+// 
+// About: Examples
+// 
+// This working example, complete with fully commented code, illustrates a few
+// ways in which this plugin can be used.
+// 
+// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
+// 
+// About: Support and Testing
+// 
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+// 
+// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
+// Unit Tests      - http://benalman.com/code/projects/jquery-resize/unit/
+// 
+// About: Release History
+// 
+// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
+//       immediately after bind in some circumstances. Also changed $.fn.data
+//       to $.data to improve performance.
+// 1.0 - (2/10/2010) Initial release
+
+(function($,window,undefined){
+  '$:nomunge'; // Used by YUI compressor.
+  
+  // A jQuery object containing all non-window elements to which the resize
+  // event is bound.
+  var elems = $([]),
+    
+    // Extend $.resize if it already exists, otherwise create it.
+    jq_resize = $.resize = $.extend( $.resize, {} ),
+    
+    timeout_id,
+    
+    // Reused strings.
+    str_setTimeout = 'setTimeout',
+    str_resize = 'resize',
+    str_data = str_resize + '-special-event',
+    str_delay = 'delay',
+    str_throttle = 'throttleWindow';
+  
+  // Property: jQuery.resize.delay
+  // 
+  // The numeric interval (in milliseconds) at which the resize event polling
+  // loop executes. Defaults to 250.
+  
+  jq_resize[ str_delay ] = 250;
+  
+  // Property: jQuery.resize.throttleWindow
+  // 
+  // Throttle the native window object resize event to fire no more than once
+  // every <jQuery.resize.delay> milliseconds. Defaults to true.
+  // 
+  // Because the window object has its own resize event, it doesn't need to be
+  // provided by this plugin, and its execution can be left entirely up to the
+  // browser. However, since certain browsers fire the resize event continuously
+  // while others do not, enabling this will throttle the window resize event,
+  // making event behavior consistent across all elements in all browsers.
+  // 
+  // While setting this property to false will disable window object resize
+  // event throttling, please note that this property must be changed before any
+  // window object resize event callbacks are bound.
+  
+  jq_resize[ str_throttle ] = true;
+  
+  // Event: resize event
+  // 
+  // Fired when an element's width or height changes. Because browsers only
+  // provide this event for the window element, for other elements a polling
+  // loop is initialized, running every <jQuery.resize.delay> milliseconds
+  // to see if elements' dimensions have changed. You may bind with either
+  // .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
+  // 
+  // Usage:
+  // 
+  // > jQuery('selector').bind( 'resize', function(e) {
+  // >   // element's width or height has changed!
+  // >   ...
+  // > });
+  // 
+  // Additional Notes:
+  // 
+  // * The polling loop is not created until at least one callback is actually
+  //   bound to the 'resize' event, and this single polling loop is shared
+  //   across all elements.
+  // 
+  // Double firing issue in jQuery 1.3.2:
+  // 
+  // While this plugin works in jQuery 1.3.2, if an element's event callbacks
+  // are manually triggered via .trigger( 'resize' ) or .resize() those
+  // callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
+  // events system. This is not an issue when using jQuery 1.4+.
+  // 
+  // > // While this works in jQuery 1.4+
+  // > $(elem).css({ width: new_w, height: new_h }).resize();
+  // > 
+  // > // In jQuery 1.3.2, you need to do this:
+  // > var elem = $(elem);
+  // > elem.css({ width: new_w, height: new_h });
+  // > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
+  // > elem.resize();
+      
+  $.event.special[ str_resize ] = {
+    
+    // Called only when the first 'resize' event callback is bound per element.
+    setup: function() {
+      // Since window has its own native 'resize' event, return false so that
+      // jQuery will bind the event using DOM methods. Since only 'window'
+      // objects have a .setTimeout method, this should be a sufficient test.
+      // Unless, of course, we're throttling the 'resize' event for window.
+      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+      
+      var elem = $(this);
+      
+      // Add this element to the list of internal elements to monitor.
+      elems = elems.add( elem );
+      
+      // Initialize data store on the element.
+      $.data( this, str_data, { w: elem.width(), h: elem.height() } );
+      
+      // If this is the first element added, start the polling loop.
+      if ( elems.length === 1 ) {
+        loopy();
+      }
+    },
+    
+    // Called only when the last 'resize' event callback is unbound per element.
+    teardown: function() {
+      // Since window has its own native 'resize' event, return false so that
+      // jQuery will unbind the event using DOM methods. Since only 'window'
+      // objects have a .setTimeout method, this should be a sufficient test.
+      // Unless, of course, we're throttling the 'resize' event for window.
+      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+      
+      var elem = $(this);
+      
+      // Remove this element from the list of internal elements to monitor.
+      elems = elems.not( elem );
+      
+      // Remove any data stored on the element.
+      elem.removeData( str_data );
+      
+      // If this is the last element removed, stop the polling loop.
+      if ( !elems.length ) {
+        clearTimeout( timeout_id );
+      }
+    },
+    
+    // Called every time a 'resize' event callback is bound per element (new in
+    // jQuery 1.4).
+    add: function( handleObj ) {
+      // Since window has its own native 'resize' event, return false so that
+      // jQuery doesn't modify the event object. Unless, of course, we're
+      // throttling the 'resize' event for window.
+      if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
+      
+      var old_handler;
+      
+      // The new_handler function is executed every time the event is triggered.
+      // This is used to update the internal element data store with the width
+      // and height when the event is triggered manually, to avoid double-firing
+      // of the event callback. See the "Double firing issue in jQuery 1.3.2"
+      // comments above for more information.
+      
+      function new_handler( e, w, h ) {
+        var elem = $(this),
+          data = $.data( this, str_data );
+        
+        // If called from the polling loop, w and h will be passed in as
+        // arguments. If called manually, via .trigger( 'resize' ) or .resize(),
+        // those values will need to be computed.
+        data.w = w !== undefined ? w : elem.width();
+        data.h = h !== undefined ? h : elem.height();
+        
+        old_handler.apply( this, arguments );
+      };
+      
+      // This may seem a little complicated, but it normalizes the special event
+      // .add method between jQuery 1.4/1.4.1 and 1.4.2+
+      if ( $.isFunction( handleObj ) ) {
+        // 1.4, 1.4.1
+        old_handler = handleObj;
+        return new_handler;
+      } else {
+        // 1.4.2+
+        old_handler = handleObj.handler;
+        handleObj.handler = new_handler;
+      }
+    }
+    
+  };
+  
+  function loopy() {
+    
+    // Start the polling loop, asynchronously.
+    timeout_id = window[ str_setTimeout ](function(){
+      
+      // Iterate over all elements to which the 'resize' event is bound.
+      elems.each(function(){
+        var elem = $(this),
+          width = elem.width(),
+          height = elem.height(),
+          data = $.data( this, str_data );
+        
+        // If element size has changed since the last time, update the element
+        // data store and trigger the 'resize' event.
+        if ( width !== data.w || height !== data.h ) {
+          elem.trigger( str_resize, [ data.w = width, data.h = height ] );
+        }
+        
+      });
+      
+      // Loop.
+      loopy();
+      
+    }, jq_resize[ str_delay ] );
+    
+  };
+  
+})(jQuery,this);