We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 42562
    • 1,145 Posts
    When it comes to AJAX chat solutions, this one ranks pretty pretty high, for free.
    Server load, is a topic for discussion.
    So last night I got to pondering how to really and fully integrate into MODx this:
    https://frug.github.io/AJAX-Chat/

    The result is this rather brief tutorial and demo:
    http://www.leofec.com/modx-revolution/modx-integrated-blueimp-ajax-chat-plugin.html

    You can set Chat Sudo Admin, and Chat Moderators, and Ordinary Users, and Guests, using MODx userid/usergroup or any other MODx user criteria you want.
    Set what Chatroom, what group/user can view etc.
    I don't have time to make this into an Extra yet.

    But I hope someone/community benefits from it.

    And mind you, it is easy to auto generate the chatrooms, say from a list of parent resources or forum whathaveyou.
    The plugin, by itself boats of these

    FEATURES
    Easy installation
    Usable as shoutbox
    Multiple channels
    Private messaging
    Private channels
    Invitation system
    Kick/Ban or Ignore offending Users
    Online users list with user menu
    Emoticons/Smilies
    Easy way to add custom emoticons
    BBCode support
    Optional Flash based sound support
    Optional visual update information (changing window title)
    Clickable Hyperlinks
    Splitting of long words to preserve chat layout
    Flood control
    Possibility to delete messages inside the chat
    IRC style commands
    Easy interface to add custom commands
    Possibility to define opening hours for the chat
    Possibility to enable/disable guest users
    Persistent client-side settings
    Multiple languages (auto-detection of ACCEPT_LANGUAGE browser setting)
    Multiple styles with easy layout customization through stylesheets (CSS) and templates
    Automatic adjustment of displayed time to local client timezone
    Standards compliance (XHTML 1.0 strict)
    Accepts any text input, including code and special characters
    Multiline input field with the possibility to enter line breaks
    Message length counter
    Realtime monitoring and logs viewer
    Support for unicode (UTF-8) and non-unicode content types
    Bandwidth saving update calls (only updated data is sent)
    Optional support to push updates over a Flash based socket connection (increased performance and responsiveness)
    Survives connection timeouts
    Easy integration into existing authentication systems
    Sample phpBB3, MyBB, PunBB, SMF and vBulletin integrations available
    Separation of layout and code
    Well commented Source Code
    Developed with Security as integral part - built to prevent Code injections, SQL injections, Cross-site scripting (XSS), Session stealing and other attacks
    Tested successfully with Microsoft Internet Explorer, Mozilla Firefox, Opera and Safari - built to work with all modern browsers
      TinymceWrapper: Complete back/frontend content solution.
      Harden your MODX site by passwording your three main folders: core, manager, connectors and renaming your assets (thank me later!)
      5 ways to sniff / hack your own sites; even with renamed/hidden folders, burst them all up, to see how secure you are not.
      • 11055 ☆ A M B ☆
      • 3,112 Posts
      Good tutorial.

      I had successfully integrated this chat into MODX, too, sometimes ago.
      But it lacked some of features the client needed, eg: hard to edit opening time, bad words, etc.
      And it overloaded server by concurrent AJAX requests.

      Right now we are using an extra, that based on WebSocket, based on RatchetPhp.
      I added those specific requirements into a CMP.
      I had tried to use nodejs, but it was hard to integrate user's session between JS & PHP languages.
      I'll try to create a tut on this when have time. [ed. note: goldsky last edited this post 11 years, 9 months ago.]
        Rico
        Genius is one percent inspiration and ninety-nine percent perspiration. Thomas A. Edison
        MODx is great, but knowing how to use it well makes it perfect!

        www.virtudraft.com

        Security, security, security! | Indonesian MODx Forum | MODx Revo's cheatsheets | MODx Evo's cheatsheets

        Author of Easy 2 Gallery 1.4.x, PHPTidy, spieFeed, FileDownload R, Upload To Users CMP, Inherit Template TV, LexRating, ExerPlan, Lingua, virtuNewsletter, Grid Class Key, SmartTag, prevNext

        Maintainter/contributor of Babel

        Because it's hard to follow all topics on the forum, PING ME ON TWITTER @_goldsky if you need my help.
        • 42562
        • 1,145 Posts
        Very very sleek! Just now learnt of RachetPhp, wow. Thanks for the share.
          TinymceWrapper: Complete back/frontend content solution.
          Harden your MODX site by passwording your three main folders: core, manager, connectors and renaming your assets (thank me later!)
          5 ways to sniff / hack your own sites; even with renamed/hidden folders, burst them all up, to see how secure you are not.
          • 42562
          • 1,145 Posts
          Looking through the specs, the AJAXChat has socket connection option https://github.com/Frug/AJAX-Chat/wiki/Socket-server
          Did you ever try this? Is it anywhere similar to the websocket that RatchetPhp chat uses?
            TinymceWrapper: Complete back/frontend content solution.
            Harden your MODX site by passwording your three main folders: core, manager, connectors and renaming your assets (thank me later!)
            5 ways to sniff / hack your own sites; even with renamed/hidden folders, burst them all up, to see how secure you are not.
            • 11055 ☆ A M B ☆
            • 3,112 Posts
            I am not good at Ruby, so I avoided it. smiley

            With Ratchet, you can pass $modx object into the chat's class file, which you can use to get the username/fullname of the Logged In user. They are PHP smiley.

            Btw, I'll share what I have and what I can.
            Hopefully this could help to speed up your learning.

            I used https://github.com/cboden/Ratchet-examples as the base.
            After you run its composer, you'll get more file.

            This is the server.php I used.
            <?php
            // Dear FIG: Thank you for PSR-0!
            use Ratchet\Server\IoServer;
            use Ratchet\Http\HttpServer;
            use Ratchet\Server\FlashPolicy;
            use Ratchet\WebSocket\WsServer;
            use Ratchet\Wamp\ServerProtocol;
            
            use React\EventLoop\Factory;
            use React\Socket\Server as Reactor;
            use React\ZMQ\Context;
            
            /**
             * Make sure you register this namespace in autoloading
             */
            use MyPackage\Website\Bot;
            use MyPackage\Website\ChatRoom; // This is your custom Class you need to focus on
            use MyPackage\Website\PortLogger;
            use MyPackage\Cookbook\NullComponent;
            use MyPackage\Cookbook\MessageLogger;
            
            use Monolog\Logger;
            use Monolog\Handler\StreamHandler;
            
            // Composer: The greatest thing since sliced bread
            require dirname(__DIR__) . '/vendor/autoload.php';
            
            // Setup logging
            $stdout = new StreamHandler('php://stdout');
            $logout = new Logger('Sock-Out');
            $login  = new Logger('Sock-In');
            $login->pushHandler($stdout);
            $logout->pushHandler($stdout);
            
            // The all mighty event loop
            $loop = Factory::create();
            //$loop->run();
            
            // This little thing is to check for connectivity...
            // As a case study, when people connect on port 80, we're having them
            //  also connect on port 9000 and increment a counter if they connect.
            // Later, we can publish the results and find out if WebSockets over 
            //  a port other than 80 is viable (theory is blocked by firewalls).
            $context = new Context($loop);
            $push = $context->getSocket(ZMQ::SOCKET_PUSH);
            $push->connect('tcp://127.0.0.1:5555');
            
            // Setup our Ratchet ChatRoom application
            $webSock = new Reactor($loop);
            $webSock->listen(8080, '0.0.0.0');
            
            /**
             * To get changes from MODX's Custom Manager Page
             * @link http://socketo.me/docs/push
             */
            $chatRoom = new ChatRoom();
            $pull = $context->getSocket(ZMQ::SOCKET_PULL);
            $pull->bind('tcp://127.0.0.1:5555'); // Binding to 127.0.0.1 means the only client that can connect is itself
            $pull->on('message', array($chatRoom, 'onManagerEntry'));
            
            $webServer = new IoServer(// Basic I/O with clients, aww yeah
                new HttpServer(
                    new WsServer(// Boom! WebSockets
                        new PortLogger($push, 8080, // Compare vs the almost over 9000 conns
            //                new MessageLogger(// Log events in case of "oh noes"
                                new ServerProtocol(// WAMP; the new hotness sub-protocol
                                    new Bot(// People kept asking me if I was a bot, so I made one!
                                        $chatRoom // ...and DISCUSS!
                                    )
                                )
            //                , $login
            //                , $logout
            //                )
                        )
                    )
                    , $webSock
                    , $loop
                )
                , $webSock
                , $loop
            );
            //$webServer->run();
            
            // Allow Flash sockets (Internet Explorer) to connect to our app
            $flashSock = new Reactor($loop);
            $flashSock->listen(843, '0.0.0.0');
            $policy = new FlashPolicy;
            $policy->addAllowedAccess('*', 8080);
            $webServer = new IoServer($policy, $flashSock, $loop);
            //$webServer->run();
            
            $logSock = new Reactor($loop);
            $logSock->listen(9000, '0.0.0.0');
            $zLogger = new IoServer(
                new WsServer(
                    new PortLogger($push, 9000, new NullComponent)
                )
                , $logSock
                , $loop
            );
            //$zLogger->run();
            
            // GO GO GO!
            $loop->run();
            


            To run this chat server in background, I used node's forever package.
            PS:
            I only started to run this when I didn't want to check server's log in terminal.
            Otherwise, I just run $ php /full/path/to/server.php

            forever start -c php /full/path/to/httpdocs/core/components/mypackage/bin/server.php

            I also added this line into /etc/init.d/httpd to make sure the chat server auto restarting when the Apache goes auto restart for some reason. And it happens quote often.
            case "$1" in
              start)
            	start
            	/usr/local/bin/forever stopall
            	/usr/local/bin/forever start -c php /full/path/to/httpdocs/core/components/mypackage/bin/server.php &
            	;;
            


            Then in the ChatRoom.php, generally it should look like this.
            PS:
            I changed transport.js (which you'll get after run composer) and adjusted it to fit my need in this class file

            <?php
            
            namespace MyPackage\Website;
            
            use Ratchet\Wamp\WampServerInterface;
            use Ratchet\ConnectionInterface;
            use Guzzle\Http\Message\Request;
            
            class ChatRoom implements WampServerInterface {
            
                const CTRL_PREFIX = 'ctrl:';
                const CTRL_ROOMS = 'ctrl:rooms';
            
                public $modx;
                protected $rooms = array();
            
                /**
                 * Search room's ID by room's name
                 * @var array
                 */
                protected $roomLookup = array();
                // additionals
                /**
                 * Search room's name by room's ID
                 * @var array
                 */
                protected $roomNameLookup = array();
            
                /**
                 * Search user's resource(s) by user's ID
                 * @see closeUser
                 * @see banUser
                 * @var object instanceof ConnectionInterface
                 */
                protected $uidLookup = array();
                private $_mypackage = null;
                private $_filteredWords = array();
                
                public function __construct() {
                    $this->rooms[static::CTRL_ROOMS] = new \SplObjectStorage;
            
                    echo "Ratchet Server is connected!\n";
                }
            
                /**
                 * re-setup MODX, to assure MySQL connection
                 * @param ConnectionInterface $conn
                 * @return boolean
                 */
                protected function loadMODX(ConnectionInterface $conn) {
                    if (!class_exists('modX')) {
                        $classFile = dirname(dirname(dirname(dirname(dirname(__FILE__))))) . DIRECTORY_SEPARATOR . 'model/modx/modx.class.php';
                        if (!file_exists($classFile)) {
                            return $conn->close();
                        }
                        require_once($classFile);
                    }
                    $this->modx = new \modX();
                    $this->modx->initialize('web');
                    if (!$this->modx->lexicon) {
                        $this->modx->getService('lexicon','modLexicon');
                    }
                    $this->modx->lexicon->load('mypackage:default');
                    $defaultMyPackageCorePath = $this->modx->getOption('core_path') . 'components/mypackage/';
                    $mypackageCorePath = $this->modx->getOption('mypackage.core_path', null, $defaultMyPackageCorePath);
                    $this->_mypackage = $this->modx->getService('mypackage', 'MyPackage', $mypackageCorePath . 'model/');
                    /**
                     * This opening time is managed in Custom Manager Page
                     */
                    if ($this->_mypackage->isInOpeningTime() === false) {
                        return $conn->close();
                    }
                    /**
                     * Initially loads the filtered words, which also managed in Custom Manager Page
                     */
                    $this->_filteredWords = $this->_mypackage->getFilteredWords();
                    return true;
                }
            
                /**
                 * {@inheritdoc}
                 */
                public function onCall(ConnectionInterface $conn, $id, $fn, array $params) {
                    switch ($fn) {
                        case 'setName':
                            // I left this blank, because I used MODX's username below
                            break;
                        /**
                         * I added this event to get user's Object.
                         * You need to edit transport.js to add this event.
                         */
                        case 'setUid':
                            $uid = (int) $this->escape($params[0]);
                            if (empty($uid)) {
                                return $conn->callError($id, 'setUid', 'ID can not be empty');
                            }
            
                            $this->loadMODX($conn);
            
                            $user = $this->modx->getObject('modUser', $uid);
                            if (empty($user)) {
                                return $conn->callError($id, 'setUid', 'User was not found for $uid: ' . $uid);
                            }
                            $userName = $user->get('username');
                            $profile = $user->getOne('Profile');
                            $fullName = $profile->get('fullname');
                            $conn->Chat->uid = $uid;
                            $conn->Chat->username = $userName;
                            $conn->Chat->name = !empty($fullName) ? $fullName : $userName;
                            $conn->Chat->email = $profile->get('email'); // for avatar
                            
                            /**
                             * I added this custom property just for personal preference
                             */
                            $this->uidLookup[$uid] = $conn;
            
                            /* return */ $conn->callResult($id, array(
                                'uid' => $uid,
                                'username' => $conn->Chat->username,
                                'name' => $conn->Chat->name,
                                'ug' => $conn->Chat->usergroup,
                                'avatar' => md5(strtolower(trim($conn->Chat->email))) // Adding nice Gravatar here
                            ));
                            break;
                        case 'createRoom':
                            // code
                            break;
                        case 'closeUser':
                            // code
                            break;
                        case 'banUser':
                            // code
                            break;
                        default:
                            /* return */ $conn->callError($id, '', 'Unknown call');
                    }
                }
            
                protected function filterMessage($message) {
                    foreach ($this->_filteredWords as $pattern => $replace) {
                        $message = preg_replace('/' . preg_quote($pattern, '/') . '/i', $replace, $message);
                    }
                    return $message;
                }
            
                /**
                 * {@inheritdoc}
                 */
                public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude = array(), array $eligible = array()) {
                    $event = (string) $event;
                    if (empty($event)) {
                        return;
                    }
                    if (!array_key_exists($topic, $conn->Chat->rooms) || !array_key_exists($topic, $this->rooms) || $this->isControl($topic)) {
                        // error, can not publish to a room you're not subscribed to
                        // not sure how to handle error - WAMP spec doesn't specify
                        // for now, we're going to silently fail
                        $this->debug("'{$conn->Chat->name}' is not subscribed in '{$topic}'", $event, __LINE__);
                        return;
                    }
            
                    $rawMessage = $event;
                    $event = $this->filterMessage($event);
                    $event = $this->escape($event);
                    $sentMessage = $event;
            
                    $this->broadcast($topic, array(
                        'message',
                        $conn->Chat->uid,
                        $event,
                        date('c'),
                        $conn->remoteAddress
                    ));
                    // store message into DB
                    $this->_mypackage->recordMessage($conn->Chat->name, $conn->Chat->uid, $conn->Chat->name, $rawMessage, $sentMessage);
                }
            
                /**
                 * @param string JSON'ified string we'll receive from ZeroMQ
                 * @link http://socketo.me/docs/push
                 */
                public function onManagerEntry($entry) {
                    $entryData = json_decode($entry, true);
                    if (!is_array($entryData) || empty($entryData)) {
                        return false;
                    }
                    switch ($entryData['type']) {
                        case 'filteredWord':
                            $this->_filteredWords[$entryData['data']['pattern']] = $entryData['data']['replace'];
                            break;
                        case 'filteredWordRemoved':
                            unset($this->_filteredWords[$entryData['data']['pattern']]);
                            break;
                        default:
            
                            break;
                    }
                }
            
                // more methods.....
            }
            


            I can't release my package, because my chat is highly customized with specific needs, eg: private rooms and more restrictive permissions.

            I hope you can get the idea.
            Throw more question if you want to.
              Rico
              Genius is one percent inspiration and ninety-nine percent perspiration. Thomas A. Edison
              MODx is great, but knowing how to use it well makes it perfect!

              www.virtudraft.com

              Security, security, security! | Indonesian MODx Forum | MODx Revo's cheatsheets | MODx Evo's cheatsheets

              Author of Easy 2 Gallery 1.4.x, PHPTidy, spieFeed, FileDownload R, Upload To Users CMP, Inherit Template TV, LexRating, ExerPlan, Lingua, virtuNewsletter, Grid Class Key, SmartTag, prevNext

              Maintainter/contributor of Babel

              Because it's hard to follow all topics on the forum, PING ME ON TWITTER @_goldsky if you need my help.
              • 35756
              • 166 Posts
              profilneurotiker Reply #6, 11 years ago
              Hi donshakespeare!

              Additional to my question about the HybridAuth-issue I have (https://forums.modx.com/thread/96627/has-anyone-got-hybridauth-working-in-revo-2-3-and-how#dis-post-533300) I want to add a chat to my site.

              So I tried to follow your instructions you posted on your site:


              1. Get and unzip Blueimp's AJAX Chat (0.8.7+)
              2. Copy the folder chat to your assets folder, e.g: assets/chat/
              3. After installing, open assets/chat/lib/custom.php and initialize MODx.
              4. Copy this sweet code - ... and replace everything in assets/chat/lib/CustomAJAXChat.php
              5. Customize the rest of Ajax Chat accordingly via assets/chat/lib/config.php

              So I downloaded the zip-file and uploaded it to my folder "assets/chat".
              Then I opened the assets/chat/lib/custom.php and added this to the file:

              require_once '/absolute/path/to/modx/config.core.php';
              require_once MODX_CORE_PATH.'model/modx/modx.class.php';
              $modx = new modX();
              $modx->initialize('web');
              $modx->getService('error','error.modError', '', '');


              I entered the correct path to the config-core.php-file in my root-directory.

              After that - in step 4 - I looked for the mentioned file "CustomAJAXChat.php". But this file is inside this folder "assets/chat/lib/class/CustomAJAXChat.php", not in the folder you mentioned. But I used the file inside the class-folder and replaced everything in it with the code you provide on your site.

              Then I added this at the bottom of "assets/chat/lib/config.php" (the file didn't exist, so I copied the whole content of the "config.php.example"-file to a newly created "config.php"-file):

              $config['forceAutoLogin'] = false;
              $config['guestUserSuffix'] = '(Guest)';
              $config['allowNickChange'] = false;


              But what to do then? I need to add something to my template for the chat to being displayed I think?

              So I treid to add the content of the "CustomAJAXChat.php" into a new snippet called "Chat" and added this to my template (like this: [[!Chat]]), but then I get the following error message:
              Fatal error: Class 'AJAXChat' not found in /homepages/xx/xxxxxxx/htdocs/modx/myplug/core/cache/includes/elements/modsnippet/5.include.cache.php on line 11


              and this is the content of the snippet:
              <?php
              /*
               * @package AJAX_Chat
               * @author Sebastian Tschan
               * @copyright (c) Sebastian Tschan
               * @license GNU Affero General Public License
               * @link https://blueimp.net/ajax/
              */
               
              class CustomAJAXChat extends AJAXChat
              {
                /****
              Use this to initialize MODx in /assets/chat/lib/custom.php
                  if (!defined('MODX_CORE_PATH')) {
                      define('MODX_CORE_PATH', '/path/to/modx/core/');
                  }
                  if (!defined('MODX_CONFIG_KEY')) {
                      define('MODX_CONFIG_KEY', 'config');
                  }
                  require_once( MODX_CORE_PATH . 'model/modx/modx.class.php');
              *****/
              ...


              Can you give me any additional tip on how to implement the chat on my site?



              EDIT: I also tried to delete the uncommented-part of the CustomAJAXChat.php-file like this:
              <?php
              /*
               * @package AJAX_Chat
               * @author Sebastian Tschan
               * @copyright (c) Sebastian Tschan
               * @license GNU Affero General Public License
               * @link https://blueimp.net/ajax/
              */
               
              class CustomAJAXChat extends AJAXChat
              {
              
              Use this to initialize MODx in /assets/chat/lib/custom.php
                  if (!defined('MODX_CORE_PATH')) {
                      define('MODX_CORE_PATH', '/homepages/xx/xxxxxxx/htdocs/modx/myplug/core/');
                  }
                  if (!defined('MODX_CONFIG_KEY')) {
                      define('MODX_CONFIG_KEY', 'config');
                  }
                  require_once( MODX_CORE_PATH . 'model/modx/modx.class.php');


              but this results in a new error:

              Parse error: syntax error, unexpected 'to' (T_STRING), expecting ',' or ';' or '{' in /homepages/xx/xxxxxxx/htdocs/modx/myplug/core/cache/includes/elements/modsnippet/5.include.cache.php on line 13
              [ed. note: profilneurotiker last edited this post 11 years ago.]
                • 42562
                • 1,145 Posts
                donshakespeare Reply #7, 11 years ago
                profilneurotiker if you are interested in this chat, and encourage me, I can turn the tutorial into a rather powerful MODx extra.
                  TinymceWrapper: Complete back/frontend content solution.
                  Harden your MODX site by passwording your three main folders: core, manager, connectors and renaming your assets (thank me later!)
                  5 ways to sniff / hack your own sites; even with renamed/hidden folders, burst them all up, to see how secure you are not.
                  • 28042 ☆ A M B ☆
                  • 24,524 Posts
                  Will my encouragement help?
                    Studying MODX in the desert - http://sottwell.com
                    Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
                    Join the Slack Community - http://modx.org
                    • 35756
                    • 166 Posts
                    profilneurotiker Reply #9, 11 years ago
                    Oh, I would definately appreciate your effort on that! Don't know what you're expecting though? wink But if it helps as well, I want to encourage you to do so a lot!
                      • 42562
                      • 1,145 Posts
                      donshakespeare Reply #10, 11 years ago
                      Two solid interests, good!
                      Work will begin on it asap. If you like the finished extra, send as many donations as your heart desires.

                      My demo has some modifications, gravatar, youtube etc, I'll add those because I like them. I'll try and make them optional though.
                      Cheers
                        TinymceWrapper: Complete back/frontend content solution.
                        Harden your MODX site by passwording your three main folders: core, manager, connectors and renaming your assets (thank me later!)
                        5 ways to sniff / hack your own sites; even with renamed/hidden folders, burst them all up, to see how secure you are not.