
.<?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();
forever start -c php /full/path/to/httpdocs/core/components/mypackage/bin/server.php
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 & ;;
<?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.....
}
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
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', '', '');$config['forceAutoLogin'] = false; $config['guestUserSuffix'] = '(Guest)'; $config['allowNickChange'] = false;
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
<?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');
*****/
...
<?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');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
But if it helps as well, I want to encourage you to do so a lot!