Explorar el Código

Ticket #2965 - Initial commit.

AntonLV hace 6 años
padre
commit
703ff9b465

+ 103 - 0
modules/boonex/reminders/classes/BxRemindersConfig.php

@@ -0,0 +1,103 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxRemindersConfig extends BxBaseModGeneralConfig
+{
+    protected $_oDb;
+
+    /**
+     * Is needed to send system notifications from.
+     */
+    protected $_iSystemProfileId;
+
+    function __construct($aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->CNF = array (
+            // module icon
+            'ICON' => 'bell col-red3',    
+
+            // database tables
+            'TABLE_ENTRIES' => $aModule['db_prefix'] . 'entries',
+            'TABLE_TYPES' => $aModule['db_prefix'] . 'types',
+
+            // database fields
+            'FIELD_ID' => 'id',
+            'FIELD_AUTHOR' => 'author',
+            'FIELD_ADDED' => 'added',
+            'FIELD_CHANGED' => 'changed',
+            'FIELD_TITLE' => 'title',
+            'FIELD_TEXT' => 'text',
+            'FIELD_LINK' => 'link',
+
+            // page URIs
+            'URI_VIEW_ENTRY' => '',
+
+            // options
+            'PARAM_DELETE_AFTER' => 'bx_reminders_delete_after',
+            'PARAM_DELETE_SYSTEM_PROFILE_ID' => 'bx_reminders_system_profile_id',
+            'PARAM_DAYS_DELIMITER' => ',',
+
+            // objects
+            'OBJECT_CONNECTIONS_FRD' => 'sys_profiles_friends',
+            'OBJECT_CONNECTIONS_SBN' => 'sys_profiles_subscriptions',
+
+            // modules
+            'MODULE' => $this->getName(),
+            'MODULE_PROFILES' => 'bx_organizations',
+            'MODULE_CUSTOMS' => 'bx_abundantmods',
+
+            // some language keys
+            'T' => array (
+                'txt_sample_single' => '_bx_reminders_txt_sample_single',
+            	'txt_sample_single_with_article' => '_bx_reminders_txt_sample_single_with_article',
+            )
+        );
+
+        $this->_aJsClasses = array(
+            'main' => 'BxRemindersMain',
+        );
+
+        $this->_aJsObjects = array(
+            'main' => 'oBxRemindersMain',
+        );
+
+        $sPrefix = str_replace('_', '-', $this->_sName);
+        $this->_aHtmlIds = array(
+            'main' => $sPrefix . '-main',
+        );
+
+        $this->_iSystemProfileId = 2;
+    }
+
+    public function init(&$oDb) 
+    {
+        $this->_oDb = &$oDb;
+
+        $this->_iSystemProfileId = (int)$this->_oDb->getParam($this->CNF['PARAM_DELETE_SYSTEM_PROFILE_ID']);
+    }
+
+    public function getHtmlIds($sKey = '')
+    {
+        if(empty($sKey))
+            return $this->_aHtmlIds;
+
+        return isset($this->_aHtmlIds[$sKey]) ? $this->_aHtmlIds[$sKey] : '';
+    }
+    
+    public function getSystemProfileId()
+    {
+        return $this->_iSystemProfileId;
+    }
+}
+
+/** @} */

+ 371 - 0
modules/boonex/reminders/classes/BxRemindersCron.php

@@ -0,0 +1,371 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+bx_import('BxDolPrivacy');
+
+class BxRemindersCron extends BxDolCron
+{
+    protected $_sModule;
+    protected $_oModule;
+
+    public function __construct()
+    {
+    	$this->_sModule = 'bx_reminders';
+    	$this->_oModule = BxDolModule::getInstance($this->_sModule);
+
+        parent::__construct();
+    }
+
+    function processing()
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        //--- Delete expired reminders.
+        $iExpireDays = (int)$this->_oModule->_oDb->getParam($CNF['PARAM_DELETE_AFTER']);
+        if($iExpireDays >= 0)
+            $this->_oModule->_oDb->deleteEntryExpired($iExpireDays);
+
+        //--- Check and Add new reminders.
+        $iSystemProfileId = $this->_oModule->_oConfig->getSystemProfileId();
+
+        $aTypesSystem = $this->_oModule->_oDb->getType(array('type' => 'all', 'personal' => 0, 'active' => 1));
+        $bTypesSystem = !empty($aTypesSystem) && is_array($aTypesSystem);
+
+        $aTypesPersonal = $this->_oModule->_oDb->getType(array('type' => 'all', 'personal' => 1, 'active' => 1));
+        $bTypesPersonal = !empty($aTypesPersonal) && is_array($aTypesPersonal);
+        if(!$bTypesSystem && !$bTypesPersonal)
+            return;
+
+        $iNow = time();
+        list($iNowYear, $iNowMonth, $iNowDay) = explode('-', date('Y-m-j'));
+
+        /**
+         * Checkes whether some of system reminders
+         * can be shown today or not.
+         */
+        if($bTypesSystem) {
+            foreach($aTypesSystem as $iKey => $aType) {
+                list($iMonth, $iDay) = explode('-', $aType['when']);
+                $aTypesSystem[$iKey]['expired'] = mktime(0, 0, 0, $iMonth, $iDay, $iNowYear);
+
+                /**
+                 * Check whether system reminder should be shown or not.
+                 */
+                $iDays = (int)$aType['show'];
+                $aTypesSystem[$iKey]['show'] = false;
+
+                if($iDays != 0 && $this->_checkDateBegin($iNowYear . '-' . $aType['when'], $iDays, $iNowMonth, $iNowDay))
+                    $aTypesSystem[$iKey]['show'] = $iDays;
+
+                 /**
+                  * Check whether a notification about system reminder
+                  * should be sent or not.
+                  */
+                $aDays = explode($CNF['PARAM_DAYS_DELIMITER'], $aType['notify']);
+                $aTypesSystem[$iKey]['notify'] = false;
+
+                if(!empty($aDays) && is_array($aDays))
+                    foreach($aDays as $iDays) {
+                        $iDays = (int)$iDays;
+
+                        if($iDays != 0 && $this->_checkDateBegin($iNowYear . '-' . $aType['when'], $iDays, $iNowMonth, $iNowDay)) {
+                            $aTypesSystem[$iKey]['notify'] = $iDays;
+                            break;
+                        }
+                    }
+
+                if($aTypesSystem[$iKey]['show'] === false && $aTypesSystem[$iKey]['notify'] === false)
+                    unset($aTypesSystem[$iKey]);
+            }
+
+            $bTypesSystem = !empty($aTypesSystem) && is_array($aTypesSystem);
+        }
+
+        /**
+         * Get all profiles from specified profile based module
+         * and start to show reminders and/or notify about them.
+         */
+        $aProfiles = $this->_oModule->_oDb->getProfiles($CNF['MODULE_PROFILES']);
+        foreach($aProfiles as $aProfile) {
+            $iProfileId = (int)$aProfile['id'];
+
+            //--- Process personal(related to followed friends) reminders.
+            if($bTypesPersonal) {
+                $aFriendsIds = $this->_getFriendsIds($iProfileId);
+                foreach($aFriendsIds as $iFriendId) {
+                    $oFriend = BxDolProfile::getInstance($iFriendId);
+                    if(!$oFriend)
+                        continue;
+
+                    $aFriend = BxDolService::call($CNF['MODULE_PROFILES'], 'get_content_info_by_id', array($oFriend->getContentId()));
+                    if(empty($aFriend) || !is_array($aFriend))
+                        continue;
+
+                    foreach($aTypesPersonal as $aType) {
+                        $iTypeId = (int)$aType['id'];
+                        $sTypePostfix = bx_gen_method_name($aType['name']);
+
+                        $sMethod = '_checkDateBegin' . $sTypePostfix;
+                        if(!method_exists($this, $sMethod)) 
+                            continue;
+
+                        $aEntryNew = array(
+                            'type_id' => $iTypeId,
+                            'rmd_pid' => $iProfileId,
+                            'cnt_pid' => $iFriendId,
+                            'params' => '',
+                            'active' => 1,
+                            'visible' => 1,
+                            'added' => $iNow,
+                            'expired' => 0
+                        );
+
+                        //--- Perform Show.
+                        $iDays = (int)$aType['show'];
+                        if($iDays != 0 && ($aBeginDates = $this->$sMethod($oFriend, $aFriend, $iDays, $iNowYear, $iNowMonth, $iNowDay)) !== false)
+                            foreach($aBeginDates as $aBeginDate) {
+                                $aEntry = $this->_oModule->_oDb->isEntryPersonal($iTypeId, $iProfileId, $iFriendId, $aBeginDate['expired']);
+                                if($aEntry === false)
+                                    $this->_oModule->_oDb->insertEntry(array_merge($aEntryNew, array(
+                                        'params' => serialize($aBeginDate['params']),
+                                        'visible' => 1,
+                                        'expired' => $aBeginDate['expired']
+                                    )));
+                                else if((int)$aEntry['visible'] == 0)                                     
+                                    $this->_oModule->_oDb->updateEntry(array('visible' => 1), array('id' => (int)$aEntry['id']));
+                            }
+
+                        //--- Perform Notify.
+                        $aDays = explode($CNF['PARAM_DAYS_DELIMITER'], $aType['notify']);
+                        if(empty($aDays) || !is_array($aDays))
+                            continue;
+
+                        foreach($aDays as $iDays) {
+                            $iDays = (int)$iDays;
+                            if(empty($iDays))
+                                continue;
+
+                            $aBeginDates = $this->$sMethod($oFriend, $aFriend, $iDays, $iNowYear, $iNowMonth, $iNowDay);
+                            if($aBeginDates === false)
+                                continue;
+
+                            foreach($aBeginDates as $aBeginDate) {
+                                $iExpired = (int)$aBeginDate['expired'];
+
+                                $iEntry = 0;
+                                $aEntry = $this->_oModule->_oDb->isEntryPersonal($iTypeId, $iProfileId, $iFriendId, $iExpired);
+                                if($aEntry === false)
+                                    $iEntry = $this->_oModule->_oDb->insertEntry(array_merge($aEntryNew, array(
+                                        'params' => serialize($aBeginDate['params']),
+                                        'visible' => 0,
+                                        'expired' => $iExpired
+                                    )));
+                                else
+                                    $iEntry = (int)$aEntry['id'];
+
+                                /**
+                                 * Stop the process if there is no an entry to notify about.
+                                 */
+                                if(empty($iEntry)) 
+                                    continue;
+
+                                /*
+                                 * Stop the process if the action was already porformed. 
+                                 */
+                                $bNotified = !empty($aEntry['notified']) && is_array($aEntry['notified']);
+                                if($bNotified && array_key_exists($iExpired, $aEntry['notified']) && in_array($iDays, $aEntry['notified'][$iExpired]))
+                                    continue;
+
+                                bx_alert($CNF['MODULE'], 'added', $iEntry, $iFriendId, array(
+                                    'object_author_id' => $iProfileId,
+                                    'privacy_view' => BX_DOL_PG_ALL
+                                ));
+
+                                $aNotified = $bNotified ? $aEntry['notified'] : array();
+                                if(!isset($aNotified[$iExpired]))
+                                    $aNotified[$iExpired] = array();
+                                $aNotified[$iExpired][] = $iDays;
+
+                                $this->_oModule->_oDb->updateEntry(array('notified' => serialize($aNotified)), array('id' => $iEntry));
+                            }
+
+                            break;
+                        }
+                    }
+                }
+            }
+
+            //--- Process system (related to holidays) reminders.
+            if($bTypesSystem) {
+                foreach($aTypesSystem as $aType) {
+                    $iTypeId = (int)$aType['id'];
+
+                    $aEntryNew = array(
+                        'type_id' => $iTypeId,
+                        'rmd_pid' => $iProfileId,
+                        'cnt_pid' => 0,
+                        'params' => '',
+                        'active' => 1,
+                        'visible' => 1,
+                        'added' => $iNow,
+                        'expired' => 0
+                    );
+
+                    //--- Perform Show.
+                    if($aType['show'] !== false)
+                        if($this->_oModule->_oDb->isEntrySystem($iTypeId, $iProfileId) === false)
+                            $this->_oModule->_oDb->insertEntry(array_merge($aEntryNew, array(
+                                'visible' => 1,
+                                'expired' => $aType['expired']
+                            )));
+
+                    //--- Perform Notify.
+                    if($aType['notify'] !== false) {
+                        $iDays = (int)$aType['notify'];
+                        $iExpired = (int)$aType['expired'];
+
+                        $iEntry = 0;
+                        $aEntry = $mResultExists = $this->_oModule->_oDb->isEntrySystem($iTypeId, $iProfileId);
+                        if($aEntry === false)
+                            $iEntry = $this->_oModule->_oDb->insertEntry(array_merge($aEntryNew, array(
+                                'visible' => 0,
+                                'expired' => $iExpired
+                            )));
+                        else
+                            $iEntry = (int)$aEntry['id'];
+
+                        /**
+                         * Stop the process if there is no an entry to notify about.
+                         */
+                        if(empty($iEntry)) 
+                           continue;
+
+                        /*
+                         * Stop the process if the action was already porformed. 
+                         */
+                        $bNotified = !empty($aEntry['notified']) && is_array($aEntry['notified']);
+                        if($bNotified && array_key_exists($iExpired, $aEntry['notified']) && in_array($iDays, $aEntry['notified'][$iExpired]))
+                            continue;
+
+                        bx_alert($CNF['MODULE'], 'added', $iEntry, $iSystemProfileId, array(
+                            'object_author_id' => $iProfileId,
+                            'privacy_view' => BX_DOL_PG_ALL
+                        ));
+
+                        $aNotified = $bNotified ? $aEntry['notified'] : array();
+                        if(!isset($aNotified[$iExpired]))
+                            $aNotified[$iExpired] = array();
+                        $aNotified[$iExpired][] = $iDays;
+
+                        $this->_oModule->_oDb->updateEntry(array('notified' => serialize($aNotified)), array('id' => $iEntry));
+                    }
+                }
+            }
+        }
+    }
+
+    protected function _checkDateBeginBirthday(&$oProfile, &$aProfile, $iDays, $iNowYear, $iNowMonth, $iNowDay)
+    {
+        if(!isset($aProfile['date_of_birth']))
+            return false;
+
+        $mResult = $this->_checkDate($oProfile, $aProfile['date_of_birth'], $iDays, $iNowYear, $iNowMonth, $iNowDay);
+        if(!$mResult)
+            return $mResult;
+        
+        return array($mResult);
+    }
+
+    protected function _checkDateBeginMarriage(&$oProfile, &$aProfile, $iDays, $iNowYear, $iNowMonth, $iNowDay)
+    {
+        if(!isset($aProfile['marriage_date']))
+            return false;
+
+        $mResult = $this->_checkDate($oProfile, $aProfile['marriage_date'], $iDays, $iNowYear, $iNowMonth, $iNowDay);
+        if(!$mResult)
+            return $mResult;
+
+        return array($mResult);
+    }
+
+    protected function _checkDateBeginAnniversary(&$oProfile, &$aProfile, $iDays, $iNowYear, $iNowMonth, $iNowDay)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aDates = BxDolService::call($CNF['MODULE_CUSTOMS'], 'get_important_dates', array($oProfile->id()));
+        if(empty($aDates) || !is_array($aDates))
+            return false;
+
+        $aResults = array();
+        foreach($aDates as $aDate)
+            if(($aResult = $this->_checkDate($oProfile, date('Y-m-d', $aDate['important_dates_date_start']), $iDays, $iNowYear, $iNowMonth, $iNowDay)) !== false) {
+                $aResult['params']['title'] = $aDate['important_dates_description'];
+
+                $aResults[] = $aResult;
+            }
+
+        return $aResults;
+    }
+
+    protected function _checkDateBeginDeath(&$oProfile, &$aProfile, $iDays, $iNowYear, $iNowMonth, $iNowDay)
+    {
+        if(!isset($aProfile['date_of_death']))
+            return false;
+
+        $mResult = $this->_checkDate($oProfile, $aProfile['date_of_death'], $iDays, $iNowYear, $iNowMonth, $iNowDay);
+        if(!$mResult)
+            return $mResult;
+
+        return array($mResult);
+    }
+
+    protected function _checkDate(&$oProfile, $sDate, $iDays, $iNowYear, $iNowMonth, $iNowDay)
+    {
+        $bResult = $this->_checkDateBegin($sDate, $iDays, $iNowMonth, $iNowDay);
+        if(!$bResult)
+            return $bResult;
+
+        list($iYear, $iMonth, $iDay) = explode('-', $sDate);
+        $iDate = mktime(0, 0, 0, $iMonth, $iDay, $iNowYear);
+
+        return array(
+            'params' => array(
+                'profile_id' => $oProfile->id(),
+                'profile_name' => $oProfile->getDisplayName(),
+                'profile_link' => $oProfile->getUrl(),
+                'date' => bx_time_js($iDate)
+            ),
+            'expired' => $iDate
+        );
+    }
+
+    protected function _checkDateBegin($sDate, $iDays, $iNowMonth, $iNowDay) 
+    {
+        list($iMonth, $iDay) = explode('-', date('m-d', strtotime("-" . $iDays . " days", strtotime($sDate))));
+
+        return (int)$iMonth == (int)$iNowMonth && (int)$iDay == (int)$iNowDay;
+    }
+
+    /**
+     * Returns followed friends.
+     */
+    protected function _getFriendsIds($iProfileId)
+    {
+        $CNF = &$this->_oModule->_oConfig->CNF;
+
+        $aFriends = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS_FRD'])->getConnectedContent($iProfileId, true);
+        $aSubscriptions = BxDolConnection::getObjectInstance($CNF['OBJECT_CONNECTIONS_SBN'])->getConnectedContent($iProfileId);
+
+        return array_intersect($aFriends, $aSubscriptions);
+    }
+}
+
+/** @} */

+ 214 - 0
modules/boonex/reminders/classes/BxRemindersDb.php

@@ -0,0 +1,214 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Module database queries
+ */
+class BxRemindersDb extends BxBaseModGeneralDb
+{
+    function __construct(&$oConfig)
+    {
+        parent::__construct($oConfig);
+    }
+
+    public function getProfiles($sProfileType)
+    {
+        return $this->getAll("SELECT `id`, `content_id` FROM `sys_profiles` WHERE `type`=:type", array(
+            'type' => $sProfileType
+        ));
+    }
+
+    public function getType($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+    	$sWhereClause = $sLimitClause = '';
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                $sWhereClause = " AND `id`=:id";
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'all':
+                $aMethod['params'][1] = array();
+
+                if(isset($aParams['personal'])) {
+                    $aMethod['params'][1]['personal'] = $aParams['personal'];
+
+                    $sWhereClause .= " AND `personal`=:personal";
+                }
+
+                if(isset($aParams['active'])) {
+                    $aMethod['params'][1]['active'] = $aParams['active'];
+
+                    $sWhereClause .= " AND `active`=:active";
+                }
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT * FROM `" . $CNF['TABLE_TYPES'] . "` WHERE 1 " . $sWhereClause . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function getEntry($aParams)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	$aMethod = array('name' => 'getAll', 'params' => array(0 => 'query'));
+
+        $sSelectClause = "`te`.*";
+    	$sJoinClause = $sWhereClause = $sLimitClause = "";
+        switch($aParams['type']) {
+            case 'id':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'id' => $aParams['id']
+                );
+
+                if(isset($aParams['full']) && $aParams['full'] === true) {
+                    $sSelectClause .= ", `tt`.`text` AS `text`, `tt`.`link` AS `link`";
+                    $sJoinClause = "LEFT JOIN `" . $CNF['TABLE_TYPES'] . "` AS `tt` ON `te`.`type_id`=`tt`.`id`";
+                }
+
+                $sWhereClause = " AND `te`.`id`=:id";
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'reminder':
+                $aMethod['name'] = 'getRow';
+                $aMethod['params'][1] = array(
+                    'type_id' => $aParams['type_id'],
+                    'rmd_pid' => $aParams['rmd_pid']
+                );
+
+                $sWhereClause = " AND `te`.`type_id`=:type_id AND `te`.`rmd_pid`=:rmd_pid";
+
+                if(isset($aParams['cnt_pid'])) {
+                    $aMethod['params'][1]['cnt_pid'] = (int)$aParams['cnt_pid'];
+
+                    $sWhereClause .= " AND `te`.`cnt_pid`=:cnt_pid";
+                }
+
+                if(isset($aParams['expired'])) {
+                    $aMethod['params'][1]['expired'] = (int)$aParams['expired'];
+
+                    $sWhereClause .= " AND `te`.`expired`=:expired";
+                }
+
+                $sLimitClause = " LIMIT 1";
+                break;
+
+            case 'rmd_pid':
+                $aMethod['params'][1] = array(
+                    'rmd_pid' => $aParams['rmd_pid'],
+                    'active' => isset($aParams['active']) ? (int)$aParams['active'] : 1
+                );
+
+                if(isset($aParams['full']) && $aParams['full'] === true) {
+                    $sSelectClause .= ", `tt`.`text` AS `text`, `tt`.`link` AS `link`";
+                    $sJoinClause = "LEFT JOIN `" . $CNF['TABLE_TYPES'] . "` AS `tt` ON `te`.`type_id`=`tt`.`id`";
+                }
+
+                $sWhereClause .= " AND `te`.`rmd_pid`=:rmd_pid AND `te`.`active`=:active";
+
+                if(isset($aParams['visible'])) {
+                    $aMethod['params'][1]['visible'] = (int)$aParams['visible'];
+
+                    $sWhereClause .= " AND `te`.`visible`=:visible";
+                }
+
+                break;
+        }
+
+        $aMethod['params'][0] = "SELECT " . $sSelectClause . " FROM `" . $CNF['TABLE_ENTRIES'] . "` AS `te` " . $sJoinClause . " WHERE 1 " . $sWhereClause . $sLimitClause;
+        return call_user_func_array(array($this, $aMethod['name']), $aMethod['params']);
+    }
+
+    public function isEntrySystem($iTypeId, $iRmdPid)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aEntry = $this->getEntry(array('type' => 'reminder', 'type_id' => $iTypeId, 'rmd_pid' => $iRmdPid));
+        if(empty($aEntry) || !is_array($aEntry))
+            return false;
+
+        $aEntry['notified'] = !empty($aEntry['notified']) ? unserialize($aEntry['notified']) : array();
+
+        return (int)$aEntry['id'];
+    }
+
+    public function isEntryPersonal($iTypeId, $iRmdPid, $iCntPid, $iExpired)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aEntry = $this->getEntry(array('type' => 'reminder', 'type_id' => $iTypeId, 'rmd_pid' => $iRmdPid, 'cnt_pid' => $iCntPid, 'expired' => $iExpired));
+        if(empty($aEntry) || !is_array($aEntry))
+            return false;
+
+        $aEntry['notified'] = !empty($aEntry['notified']) ? unserialize($aEntry['notified']) : array();
+
+        return $aEntry;
+    }
+
+    public function insertEntry($aValues)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aValues) || !is_array($aValues))
+            return false;
+
+        return (int)$this->query("INSERT INTO `" . $CNF['TABLE_ENTRIES'] . "` SET " . $this->arrayToSQL($aValues)) > 0 ? $this->lastId() : false;
+    }
+
+    public function updateEntry($aValues, $aWhere)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        if(empty($aValues) || !is_array($aValues) || empty($aWhere) || !is_array($aWhere))
+            return false;
+
+        return (int)$this->query("UPDATE `" . $CNF['TABLE_ENTRIES'] . "` SET " . $this->arrayToSQL($aValues) . " WHERE " . $this->arrayToSQL($aWhere, ' AND ')) > 0;
+    }
+
+    public function deleteEntry($mixedId)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+    	if(!is_array($mixedId))
+            $mixedId = array($mixedId);
+
+        return (int)$this->query("DELETE FROM `" . $CNF['TABLE_ENTRIES'] . "` WHERE `id` IN (" . $this->implode_escape($mixedId) . ")") > 0;
+    }
+
+    public function deleteEntryExpired($iDays)
+    {
+        $CNF = &$this->_oConfig->CNF;           
+
+        $sWhereClause = "";
+        if($iDays > 0)
+            $sWhereClause = $this->prepareAsString("`expired` < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL ? DAY))", $iDays);
+        else if($iDays == 0)
+            $sWhereClause = $this->prepareAsString("`expired` < UNIX_TIMESTAMP()", $iDays);
+        else
+            return true;
+
+        return (int)$this->query("DELETE FROM `" . $CNF['TABLE_ENTRIES'] . "` WHERE " . $sWhereClause) > 0;
+    }
+}
+
+/** @} */

+ 149 - 0
modules/boonex/reminders/classes/BxRemindersModule.php

@@ -0,0 +1,149 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxRemindersModule extends BxBaseModGeneralModule
+{
+    function __construct(&$aModule)
+    {
+        parent::__construct($aModule);
+
+        $this->_oConfig->init($this->_oDb);
+    }
+
+    public function actionMain()
+    {
+        return echoJson(array());
+    }
+
+
+    /**
+     * Service methods
+     */
+    public function serviceGetOptionsSystemProfileId()
+    {
+        $aResult = array(
+            array('key' => '', 'value' => _t('_Select_one'))
+        );
+
+        $oProfile = BxDolProfile::getInstance();
+
+        $aAccIds = BxDolAccountQuery::getInstance()->getOperators();
+        foreach($aAccIds as $iAccId) {
+            $aPrfIds = BxDolAccount::getInstance($iAccId)->getProfilesIds();
+            foreach($aPrfIds as $iPrfId) {
+                $aResult[] = array(
+                    'key' => $iPrfId,
+                    'value' => $oProfile->getDisplayName($iPrfId)
+                );
+            }
+        }
+
+        return $aResult;
+    }
+
+    public function serviceGetBlockView($iProfileId = 0)
+    {
+        if(!isLogged())
+            return '';
+
+        if(!$iProfileId)
+            $iProfileId = bx_get_logged_profile_id();
+
+        return $this->_oTemplate->getBlockView($iProfileId);
+    }
+
+    public function serviceGetNotificationsData()
+    {
+    	$sModule = $this->_aModule['name'];
+
+        return array(
+            'handlers' => array(
+                array('group' => $sModule . '_object', 'type' => 'insert', 'alert_unit' => $sModule, 'alert_action' => 'added', 'module_name' => $sModule, 'module_method' => 'get_notifications_post', 'module_class' => 'Module', 'module_event_privacy' => ''),
+            ),
+            'settings' => array(
+                array('group' => 'content', 'unit' => $sModule, 'action' => 'added', 'types' => array('personal')),
+            ),
+            'alerts' => array(
+                array('unit' => $sModule, 'action' => 'added'),
+            )
+        );
+    }
+
+    public function serviceGetNotificationsPost($aEvent)
+    {
+        $CNF = &$this->_oConfig->CNF;
+
+        $aEntry = $this->_oDb->getEntry(array('type' => 'id', 'id' => $aEvent['object_id'], 'full' => true));
+        if(empty($aEntry) || !is_array($aEntry))
+            return array();
+
+        $aReturn = array(
+            'entry_sample' => $CNF['T']['txt_sample_single'],
+            'entry_url' => $this->getEntryUrl($aEntry),
+            'lang_key' => $aEntry[$CNF['FIELD_TEXT']]
+        );
+
+        $aParams = unserialize($aEntry['params']);
+        if(!empty($aParams) && is_array($aParams))
+            $aReturn = array_merge($aReturn, $aParams);
+
+        return $aReturn;
+    }
+
+    public function serviceCheckAllowedWithContent($sAction, $iContentId, $isPerformAction = false)
+    {
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+
+    /**
+     * Permissions methods
+     */
+    public function isAllowedView($iUserId, $bPerform = false)
+    {
+        if(isAdmin())
+            return CHECK_ACTION_RESULT_ALLOWED;
+
+        $aCheckResult = checkActionModule($iUserId, 'view', $this->getName(), $bPerform);
+        if($aCheckResult[CHECK_ACTION_RESULT] !== CHECK_ACTION_RESULT_ALLOWED)
+            return $aCheckResult[CHECK_ACTION_MESSAGE];
+     
+        return CHECK_ACTION_RESULT_ALLOWED;
+    }
+
+    public function checkAllowedEditAnyEntry ($isPerformAction = false)
+    {
+        return _t('_sys_txt_access_denied');
+    }
+
+
+    /**
+     * Auxiliary methods
+     */
+    public function getEntryUrl(&$aEntry)
+    {
+        $aParams = unserialize($aEntry['params']);
+
+        $sResult = '';
+        if(!empty($aEntry['link']))
+            $sResult = bx_replace_markers($aEntry['link'], $aParams);
+        else if(!empty($aParams['profile_link']))
+            $sResult = $aParams['profile_link'];
+        else if(!empty($aParams['profile_id']))
+            $sResult = $oProfile->getUrl((int)$aParams['profile_id']);
+        else
+            $sResult = 'javascript:void(0)';
+
+        return $sResult;
+    }
+}
+
+/** @} */

+ 50 - 0
modules/boonex/reminders/classes/BxRemindersTemplate.php

@@ -0,0 +1,50 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+/*
+ * Module representation.
+ */
+class BxRemindersTemplate extends BxBaseModGeneralTemplate
+{
+    function __construct(&$oConfig, &$oDb)
+    {
+        $this->MODULE = 'bx_reminders';
+
+        parent::__construct($oConfig, $oDb);
+    }
+
+    public function getBlockView($iProfileId)
+    {
+        $oProfile = BxDolProfile::getInstance($iProfileId);
+
+        $aEntries = $this->_oDb->getEntry(array('type' => 'rmd_pid', 'rmd_pid' => $iProfileId, 'visible' => true, 'full' => true));
+        if(empty($aEntries) || !is_array($aEntries))
+            return MsgBox(_t('_Empty'));
+
+        $aTmplVarsItems = array();
+        foreach($aEntries as $aEntry) {
+            $aParams = unserialize($aEntry['params']);
+
+            $aTmplVarsItems[] = array(
+                'id' => $aEntry['id'], 
+                'link' => $this->_oModule->getEntryUrl($aEntry),
+                'content' => bx_replace_markers(_t($aEntry['text']), $aParams)
+            );
+        }
+
+        $this->addCss(array('view.css'));
+        return $this->parseHtmlByName('block_view.html', array(
+            'bx_repeat:items' => $aTmplVarsItems
+        ));
+    }
+}
+
+/** @} */

+ 84 - 0
modules/boonex/reminders/install/config.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+$aConfig = array(
+
+    /**
+     * Main Section.
+     */
+    'type' => BX_DOL_MODULE_TYPE_MODULE,
+    'name' => 'bx_reminders',
+    'title' => 'Reminders',
+    'note' => 'Reminders module.',
+    'version' => '9.0.0',
+    'vendor' => 'BoonEx',
+    'help_url' => 'http://feed.una.io/?section={module_name}',
+
+    'compatible_with' => array(
+        '9.0.x'
+    ),
+
+    /**
+     * 'home_dir' and 'home_uri' - should be unique. Don't use spaces in 'home_uri' and the other special chars.
+     */
+    'home_dir' => 'boonex/reminders/',
+    'home_uri' => 'reminders',
+
+    'db_prefix' => 'bx_reminders_',
+    'class_prefix' => 'BxReminders',
+
+    /**
+     * Category for language keys.
+     */
+    'language_category' => 'Reminders',
+
+    /**
+     * Installation/Uninstallation Section.
+     */
+    'install' => array(
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'uninstall' => array (
+        'execute_sql' => 1,
+        'update_languages' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'enable' => array(
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable' => array (
+        'execute_sql' => 1,
+        'update_relations' => 1,
+        'clear_db_cache' => 1,
+    ),
+    'disable_failed' => array (
+        'clear_db_cache' => 1,
+    ),
+
+    /**
+     * Dependencies Section
+     */
+    'dependencies' => array(),
+
+    /**
+     * Relations Section
+     */
+    'relations' => array(
+    	'bx_notifications'
+    ),
+);
+
+/** @} */

+ 20 - 0
modules/boonex/reminders/install/installer.php

@@ -0,0 +1,20 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    Reminders Reminders
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+class BxRemindersInstaller extends BxBaseModGeneralInstaller
+{
+    function __construct($aConfig)
+    {
+        parent::__construct($aConfig);
+    }
+}
+
+/** @} */

+ 62 - 0
modules/boonex/reminders/install/langs/en.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources name="en" flag="gb" title="English">
+    <string name="_bx_reminders"><![CDATA[Reminders]]></string>
+
+    <!-- Options -->
+    <string name="_bx_reminders_option_delete_after"><![CDATA[Delete expired reminders after (in days, use -1 to disable)]]></string>
+    <string name="_bx_reminders_option_system_profile_id"><![CDATA[System profile to send notifications from]]></string>
+
+
+    <!-- Pages -->
+    <string name="_bx_reminders_page_block_title_sys_view"><![CDATA[Reminders]]></string>
+    <string name="_bx_reminders_page_block_title_view"><![CDATA[Reminders]]></string>
+
+
+    <!-- Different -->
+    <string name="_bx_reminders_type_title_birthday"><![CDATA[Birthdays]]></string>
+    <string name="_bx_reminders_type_text_birthday"><![CDATA[<b>{profile_name}</b> birthday is on <b>{date}</b>. Make a Legacy Post for them.]]></string>
+    <string name="_bx_reminders_type_title_marriage"><![CDATA[Marriage]]></string>
+    <string name="_bx_reminders_type_text_marriage"><![CDATA[<b>{profile_name}</b> was married on <b>{date}</b>. Send them a Legacy Question.]]></string>
+    <string name="_bx_reminders_type_title_anniversary"><![CDATA[Anniversaries]]></string>
+    <string name="_bx_reminders_type_text_anniversary"><![CDATA[<b>{profile_name}</b> celebrates <b>{title}</b> on <b>{date}</b>. Send them a Legacy Question.]]></string>
+    <string name="_bx_reminders_type_title_death"><![CDATA[Date of Passing]]></string>
+    <string name="_bx_reminders_type_text_death"><![CDATA[Make a Post to Remember <b>{profile_name}</b>]]></string>
+
+    <string name="_bx_reminders_type_title_new_year"><![CDATA[New Year]]></string>
+    <string name="_bx_reminders_type_text_new_year"><![CDATA[What do you want to do differently this year?]]></string>
+    <string name="_bx_reminders_type_title_mlk_day"><![CDATA[Martin Luther King Jr. Day]]></string>
+    <string name="_bx_reminders_type_text_mlk_day"><![CDATA[When was a time you were challenged to think about race and poverty?]]></string>
+    <string name="_bx_reminders_type_title_valentine_day"><![CDATA[Valentine's Day]]></string>
+    <string name="_bx_reminders_type_text_valentine_day"><![CDATA[When was the first time you fell in love?]]></string>
+    <string name="_bx_reminders_type_title_start_of_spring"><![CDATA[Start of Spring]]></string>
+    <string name="_bx_reminders_type_text_start_of_spring"><![CDATA[Who do you see growing around you?]]></string>
+    <string name="_bx_reminders_type_title_easter"><![CDATA[Easter]]></string>
+    <string name="_bx_reminders_type_text_easter"><![CDATA[What role has Jesus played in your life?]]></string>
+    <string name="_bx_reminders_type_title_memorial_day"><![CDATA[Memorial Day]]></string>
+    <string name="_bx_reminders_type_text_memorial_day"><![CDATA[Tell a story from your or your family member sacrifice themselves for someone else's benefit]]></string>
+    <string name="_bx_reminders_type_title_start_of_summer"><![CDATA[Start of Summer]]></string>
+    <string name="_bx_reminders_type_text_start_of_summer"><![CDATA[Make a Legacy Post about your most important vacation]]></string>
+    <string name="_bx_reminders_type_title_4th_july"><![CDATA[July 4th]]></string>
+    <string name="_bx_reminders_type_text_4th_july"><![CDATA[What are the most important virtues in your family?]]></string>
+    <string name="_bx_reminders_type_title_labor_day"><![CDATA[Labor Day]]></string>
+    <string name="_bx_reminders_type_text_labor_day"><![CDATA[Who has the best work ethic in your family and where did they get it?]]></string>
+    <string name="_bx_reminders_type_title_start_of_fall"><![CDATA[Start of Fall]]></string>
+    <string name="_bx_reminders_type_text_start_of_fall"><![CDATA[Who has influenced you and what did they teach you?]]></string>
+    <string name="_bx_reminders_type_title_halloween"><![CDATA[Halloween]]></string>
+    <string name="_bx_reminders_type_text_halloween"><![CDATA[What is your favorite holiday to celebrate and why?]]></string>
+    <string name="_bx_reminders_type_title_veterans_day"><![CDATA[Veterans Day]]></string>
+    <string name="_bx_reminders_type_text_veterans_day"><![CDATA[Honor a service member by thanking them with Legacy]]></string>
+    <string name="_bx_reminders_type_title_thanksgiving"><![CDATA[Thanksgiving]]></string>
+    <string name="_bx_reminders_type_text_thanksgiving"><![CDATA[Who influence are you thankful for?]]></string>
+    <string name="_bx_reminders_type_title_start_of_winter"><![CDATA[Start of Winter]]></string>
+    <string name="_bx_reminders_type_text_start_of_winter"><![CDATA[What is the most important Legacy Post you can do before the end of the year?]]></string>
+    <string name="_bx_reminders_type_title_christmas"><![CDATA[Christmas]]></string>
+    <string name="_bx_reminders_type_text_christmas"><![CDATA[What is a favorite family tradition and why?]]></string>
+
+    <string name="_bx_reminders_txt_sample_single"><![CDATA[reminder]]></string>
+    <string name="_bx_reminders_txt_sample_single_with_article"><![CDATA[a reminder]]></string>
+
+    <string name=""><![CDATA[]]></string>
+    <string name=""><![CDATA[]]></string>
+    <string name=""><![CDATA[]]></string>
+</resources>

+ 16 - 0
modules/boonex/reminders/install/sql/disable.sql

@@ -0,0 +1,16 @@
+-- SETTINGS
+DELETE FROM `tot`, `toc`, `to` USING `sys_options_types` AS `tot` LEFT JOIN `sys_options_categories` AS `toc` ON `tot`.`id`=`toc`.`type_id` LEFT JOIN `sys_options` AS `to` ON `toc`.`id`=`to`.`category_id` WHERE `tot`.`name`='bx_reminders';
+
+
+-- PAGES & BLOCKS
+DELETE FROM `sys_objects_page` WHERE `module` = 'bx_reminders';
+DELETE FROM `sys_pages_blocks` WHERE `module` = 'bx_reminders' OR `object` LIKE 'bx_reminders%';
+
+
+-- 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` = @sName;
+DELETE FROM `sys_acl_actions` WHERE `Module` = 'bx_reminders';
+
+
+-- CRON
+DELETE FROM `sys_cron_jobs` WHERE `name` LIKE 'bx_reminders%';

+ 50 - 0
modules/boonex/reminders/install/sql/enable.sql

@@ -0,0 +1,50 @@
+-- SETTINGS
+SET @iTypeOrder = (SELECT MAX(`order`) FROM `sys_options_types` WHERE `group` = 'modules');
+INSERT INTO `sys_options_types`(`group`, `name`, `caption`, `icon`, `order`) VALUES 
+('modules', 'bx_reminders', '_bx_reminders', 'bx_reminders@modules/boonex/reminders/|std-icon.svg', IF(ISNULL(@iTypeOrder), 1, @iTypeOrder + 1));
+SET @iTypeId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options_categories` (`type_id`, `name`, `caption`, `order`)
+VALUES (@iTypeId, 'bx_reminders', '_bx_reminders', 1);
+SET @iCategId = LAST_INSERT_ID();
+
+INSERT INTO `sys_options` (`name`, `value`, `category_id`, `caption`, `type`, `check`, `check_error`, `extra`, `order`) VALUES
+('bx_reminders_delete_after', '1', @iCategId, '_bx_reminders_option_delete_after', 'digit', '', '', '', 1),
+('bx_reminders_system_profile_id', '', @iCategId, '_bx_reminders_option_system_profile_id', 'select', '', '', 'a:2:{s:6:"module";s:12:"bx_reminders";s:6:"method";s:29:"get_options_system_profile_id";}', 2);
+
+
+-- PAGES
+-- PAGES: add page block on dashboard
+SET @iPBCellDashboard = 3;
+SET @iPBOrderDashboard = (SELECT IFNULL(MAX(`order`), 0) FROM `sys_pages_blocks` WHERE `object` = 'sys_dashboard' AND `cell_id` = @iPBCellDashboard LIMIT 1);
+INSERT INTO `sys_pages_blocks`(`object`, `cell_id`, `module`, `title_system`, `title`, `designbox_id`, `visible_for_levels`, `type`, `content`, `deletable`, `copyable`, `active`, `order`) VALUES
+('sys_dashboard', @iPBCellDashboard, 'bx_reminders', '_bx_reminders_page_block_title_sys_view', '_bx_reminders_page_block_title_view', 11, 2147483647, 'service', 'a:2:{s:6:"module";s:12:"bx_reminders";s:6:"method";s:14:"get_block_view";}', 0, 1, 1, @iPBOrderDashboard);
+
+
+-- ACL
+INSERT INTO `sys_acl_actions` (`Module`, `Name`, `AdditionalParamName`, `Title`, `Desc`, `Countable`, `DisabledForLevels`) VALUES
+('bx_reminders', 'view', NULL, '_bx_reminders_acl_action_view', '', 0, 1);
+SET @iIdActionBlockProfile = LAST_INSERT_ID();
+
+SET @iUnauthenticated = 1;
+SET @iAccount = 2;
+SET @iStandard = 3;
+SET @iUnconfirmed = 4;
+SET @iPending = 5;
+SET @iSuspended = 6;
+SET @iModerator = 7;
+SET @iAdministrator = 8;
+SET @iPremium = 9;
+
+INSERT INTO `sys_acl_matrix` (`IDLevel`, `IDAction`) VALUES
+
+-- view
+(@iStandard, @iIdActionBlockProfile),
+(@iModerator, @iIdActionBlockProfile),
+(@iAdministrator, @iIdActionBlockProfile),
+(@iPremium, @iIdActionBlockProfile);
+
+
+-- CRON
+INSERT INTO `sys_cron_jobs` (`name`, `time`, `class`, `file`, `service_call`) VALUES
+('bx_reminders', '0 0 * * *', 'BxRemindersCron', 'modules/boonex/reminders/classes/BxRemindersCron.php', '');

+ 70 - 0
modules/boonex/reminders/install/sql/install.sql

@@ -0,0 +1,70 @@
+
+-- TABLES
+CREATE TABLE IF NOT EXISTS `bx_reminders_entries` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `type_id` int(11) NOT NULL default '0',
+  `rmd_pid` int(11) NOT NULL default '0',
+  `cnt_pid` int(11) NOT NULL default '0',
+  `params` text NOT NULL default '',
+  `notified` text NOT NULL default '',
+  `active` tinyint(4) NOT NULL default '0',
+  `visible` tinyint(4) NOT NULL default '0',
+  `added` int(11) NOT NULL,
+  `expired` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+);
+
+CREATE TABLE IF NOT EXISTS `bx_reminders_types` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `author` int(11) NOT NULL default '0',
+  `added` int(11) NOT NULL,
+  `changed` int(11) NOT NULL,
+  `name` varchar(128) NOT NULL,
+  `title` varchar(255) NOT NULL,
+  `text` varchar(255) NOT NULL,
+  `link` varchar(255) NOT NULL,
+  `when` varchar(32) NOT NULL,
+  `show` int(11) NOT NULL default '0',
+  `notify` varchar(255) NOT NULL,
+  `personal` tinyint(4) NOT NULL default '0',
+  `active` tinyint(4) NOT NULL default '0',
+  `order` int(11) NOT NULL default '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+);
+
+INSERT INTO `bx_reminders_types`(`author`, `added`, `changed`, `name`, `title`, `text`, `link`, `when`, `show`, `notify`, `personal`, `active`, `order`) VALUES
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'birthday', '_bx_reminders_type_title_birthday', '_bx_reminders_type_text_birthday', '', '', 7, '14,7,3', 1, 1, 1),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'marriage', '_bx_reminders_type_title_marriage', '_bx_reminders_type_text_marriage', '', '', 7, '14,7,3', 1, 1, 2),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'anniversary', '_bx_reminders_type_title_anniversary', '_bx_reminders_type_text_anniversary', '', '', 7, '14,7,3', 1, 1, 3),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'death', '_bx_reminders_type_title_death', '_bx_reminders_type_text_death', '', '', 14, '14,7,3', 1, 1, 4),
+
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'new_year', '_bx_reminders_type_title_new_year', '_bx_reminders_type_text_new_year', '', '12-31', 7, '14,7,3', 0, 1, 10),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'mlk_day', '_bx_reminders_type_title_mlk_day', '_bx_reminders_type_text_mlk_day', '', '01-21', 7, '14,7,3', 0, 1, 11),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'valentine_day', '_bx_reminders_type_title_valentine_day', '_bx_reminders_type_text_valentine_day', '', '02-14', 7, '14,7,3', 0, 1, 12),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'start_of_spring', '_bx_reminders_type_title_start_of_spring', '_bx_reminders_type_text_start_of_spring', '', '03-21', 7, '14,7,3', 0, 1, 13),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'easter', '_bx_reminders_type_title_easter', '_bx_reminders_type_text_easter', '', '04-21', 7, '14,7,3', 0, 1, 14),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'memorial_day', '_bx_reminders_type_title_memorial_day', '_bx_reminders_type_text_memorial_day', '', '05-27', 7, '14,7,3', 0, 1, 15),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'start_of_summer', '_bx_reminders_type_title_start_of_summer', '_bx_reminders_type_text_start_of_summer', '', '06-21', 7, '14,7,3', 0, 1, 16),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '4th_july', '_bx_reminders_type_title_4th_july', '_bx_reminders_type_text_4th_july', '', '07-04', 7, '14,7,3', 0, 1, 17),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'labor_day', '_bx_reminders_type_title_labor_day', '_bx_reminders_type_text_labor_day', '', '09-02', 7, '14,7,3', 0, 1, 18),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'start_of_fall', '_bx_reminders_type_title_start_of_fall', '_bx_reminders_type_text_start_of_fall', '', '09-21', 7, '14,7,3', 0, 1, 19),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'halloween', '_bx_reminders_type_title_halloween', '_bx_reminders_type_text_halloween', '', '10-31', 7, '14,7,3', 0, 1, 20),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'veterans_day', '_bx_reminders_type_title_veterans_day', '_bx_reminders_type_text_veterans_day', '', '11-11', 7, '14,7,3', 0, 1, 21),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'thanksgiving', '_bx_reminders_type_title_thanksgiving', '_bx_reminders_type_text_thanksgiving', '', '11-28', 7, '14,7,3', 0, 1, 22),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'start_of_winter', '_bx_reminders_type_title_start_of_winter', '_bx_reminders_type_text_start_of_winter', '', '12-21', 7, '14,7,3', 0, 1, 23),
+(0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 'christmas', '_bx_reminders_type_title_christmas', '_bx_reminders_type_text_christmas', '', '12-25', 7, '14,7,3', 0, 1, 24);
+
+
+-- STUDIO: page & widget
+INSERT INTO `sys_std_pages`(`index`, `name`, `header`, `caption`, `icon`) VALUES
+(3, 'bx_reminders', '_bx_reminders', '_bx_reminders', 'bx_reminders@modules/boonex/reminders/|std-icon.svg');
+SET @iPageId = LAST_INSERT_ID();
+
+SET @iParentPageId = (SELECT `id` FROM `sys_std_pages` WHERE `name` = 'home');
+SET @iParentPageOrder = (SELECT MAX(`order`) FROM `sys_std_pages_widgets` WHERE `page_id` = @iParentPageId);
+INSERT INTO `sys_std_widgets` (`page_id`, `module`, `url`, `click`, `icon`, `caption`, `cnt_notices`, `cnt_actions`) VALUES
+(@iPageId, 'bx_reminders', '{url_studio}module.php?name=bx_reminders', '', 'bx_reminders@modules/boonex/reminders/|std-icon.svg', '_bx_reminders', '', 'a:4:{s:6:"module";s:6:"system";s:6:"method";s:11:"get_actions";s:6:"params";a:0:{}s:5:"class";s:18:"TemplStudioModules";}');
+INSERT INTO `sys_std_pages_widgets` (`page_id`, `widget_id`, `order`) VALUES
+(@iParentPageId, LAST_INSERT_ID(), IF(ISNULL(@iParentPageOrder), 1, @iParentPageOrder + 1));
+

+ 9 - 0
modules/boonex/reminders/install/sql/uninstall.sql

@@ -0,0 +1,9 @@
+
+-- TABLES
+DROP TABLE IF EXISTS `bx_reminders_entries`, `bx_reminders_types`;
+
+
+-- STUDIO: page & widget
+DELETE FROM `tp`, `tw`, `tpw`
+USING `sys_std_pages` AS `tp`, `sys_std_widgets` AS `tw`, `sys_std_pages_widgets` AS `tpw`
+WHERE `tp`.`id` = `tw`.`page_id` AND `tw`.`id` = `tpw`.`widget_id` AND `tp`.`name` = 'bx_reminders';

+ 18 - 0
modules/boonex/reminders/request.php

@@ -0,0 +1,18 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    WOWComm WOWComm
+ * @ingroup     UnaModules
+ *
+ * @{
+ */
+
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+check_logged();
+
+BxBaseModTextRequest::processAsAction($GLOBALS['aModule'], $GLOBALS['aRequest']);
+
+/** @} */

+ 9 - 0
modules/boonex/reminders/template/block_view.html

@@ -0,0 +1,9 @@
+<div class="bx-rmr-items">
+    <bx_repeat:items>
+        <div class="bx-rmr-item bx-def-box bx-def-round-corners bx-def-color-bg-box bx-def-color-bg-box-hover bx-def-margin-sec-top-auto" id="bx-rmr-item-__id__">
+            <a href="__link__">
+                <div class="bx-rmr-item-cnt bx-def-padding-sec">__content__</div>
+            </a>
+        </div>
+    </bx_repeat:items>
+</div>

+ 1 - 0
modules/boonex/reminders/template/css/view.css

@@ -0,0 +1 @@
+@import url(../../../general/template/css/main.css);

+ 120 - 0
modules/boonex/reminders/template/images/icons/std-icon.svg

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Слой_1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 516 516"
+   style="enable-background:new 0 0 516 516;"
+   xml:space="preserve"
+   sodipodi:docname="std-icon.svg"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
+   id="metadata94"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs92" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1920"
+   inkscape:window-height="1017"
+   id="namedview90"
+   showgrid="false"
+   inkscape:zoom="0.45736434"
+   inkscape:cx="258"
+   inkscape:cy="258"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="Слой_1" />
+<style
+   type="text/css"
+   id="style71">
+	.st0{fill:url(#Shape_1_);stroke:#000000;stroke-width:4;}
+	.st1{fill:#2E1DEB;}
+</style>
+<title
+   id="title73">Lucid</title>
+<desc
+   id="desc75">Created with Sketch.</desc>
+<g
+   id="Page-1">
+	<g
+   id="Lucid"
+   transform="translate(2.000000, 2.000000)">
+		
+			<linearGradient
+   id="Shape_1_"
+   gradientUnits="userSpaceOnUse"
+   x1="-48.4082"
+   y1="652.7324"
+   x2="-48.4082"
+   y2="651.7324"
+   gradientTransform="matrix(512 0 0 -512 25041 334199)">
+			<stop
+   offset="0"
+   style="stop-color:#FFFFFF"
+   id="stop77" />
+			<stop
+   offset="1"
+   style="stop-color:#EEEEEE"
+   id="stop79" />
+		</linearGradient>
+		<path
+   id="Shape"
+   class="st0"
+   d="M256,512c112.2,0,174.5,0,215.3-40.7C512,430.5,512,368.2,512,256s0-174.5-40.7-215.3    C430.5,0,368.2,0,256,0S81.5,0,40.7,40.7C0,81.5,0,143.8,0,256s0,174.5,40.7,215.3C81.5,512,143.8,512,256,512z" />
+	</g>
+</g>
+
+<g
+   transform="matrix(0.85542487,0,0,0.85542487,37.729236,39.169486)"
+   id="g139"><path
+     style="fill:#f26004"
+     inkscape:connector-curvature="0"
+     d="m 411.404,288.506 c -18.455,-45.014 -27.685,-92.455 -27.685,-142.325 0,-31.405 -9.131,-56.337 -27.398,-74.802 -18.28,-18.464 -43.406,-29.602 -75.383,-33.407 1.52,-3.424 2.279,-6.945 2.279,-10.562 0,-7.614 -2.663,-14.091 -7.991,-19.416 C 269.898,2.663 263.427,0 255.812,0 c -7.61,0 -14.083,2.663 -19.414,7.994 -5.33,5.329 -7.993,11.802 -7.993,19.416 0,3.617 0.76,7.135 2.285,10.562 -31.977,3.809 -57.103,14.942 -75.375,33.407 -18.271,18.464 -27.406,43.396 -27.406,74.802 0,49.87 -9.229,97.311 -27.694,142.325 -18.464,45.015 -45.776,82.847 -81.942,113.492 0,9.896 3.617,18.463 10.85,25.693 7.232,7.231 15.796,10.852 25.697,10.852 h 127.907 c 0,20.178 7.135,37.404 21.411,51.675 14.277,14.273 31.504,21.412 51.678,21.412 20.177,0 37.404,-7.139 51.675,-21.412 14.273,-14.271 21.412,-31.497 21.412,-51.675 H 456.81 c 9.897,0 18.47,-3.613 25.694,-10.852 7.234,-7.23 10.852,-15.797 10.852,-25.693 -36.16,-30.645 -63.475,-68.477 -81.952,-113.492 z M 255.816,488.787 c -13.894,0 -25.747,-4.896 -35.548,-14.698 -9.801,-9.804 -14.703,-21.658 -14.703,-35.553 0,-3.046 1.524,-4.568 4.568,-4.568 3.046,0 4.57,1.522 4.57,4.568 0,11.231 4.043,20.889 12.132,28.982 8.091,8.094 17.749,12.135 28.98,12.135 3.046,0 4.575,1.53 4.575,4.569 0.002,3.046 -1.528,4.565 -4.574,4.565 z M 70.523,401.998 c 31.217,-34.454 54.674,-73.614 70.376,-117.486 15.702,-43.872 23.557,-89.982 23.557,-138.327 0,-48.727 30.452,-73.089 91.36,-73.089 60.907,0 91.361,24.362 91.361,73.089 0,48.345 7.851,94.455 23.555,138.327 15.697,43.875 39.167,83.032 70.38,117.486 z"
+     id="path137" /></g><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g141" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g143" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g145" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g147" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g149" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g151" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g153" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g155" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g157" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g159" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g161" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g163" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g165" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g167" /><g
+   transform="matrix(0.85542487,0,0,0.85542487,32.610592,36.983046)"
+   id="g169" /></svg>