Frug / AJAX-Chat

A fully customizable web chat implemented in JavaScript, PHP and MySQL which integrates nicely with common forum systems like phpBB, MyBB, FluxBB, SMF and vBulletin. A Flash and Ruby based socket connection can be used to boost performance.
http://frug.github.io/AJAX-Chat/
547 stars 300 forks source link

A proof of concept for a plugin architecture #65

Open bmanolov opened 11 years ago

bmanolov commented 11 years ago

Well, here is my example and very basic implementation of a plugin architecture for AJAX Chat: https://github.com/bmanolov/AJAX-Chat/commit/b27fdab752d087b7edd102b5201f32df2c982a98

For now it supports only custom commands but it shouldn't be that hard to introduce some other fancy plugin stuff.

All plugins live under the plugins/ directory and currently have this basic structure:

  MYPLUGIN/ (i.e. SmfKarma)
    lang/
      en.php
      de.php
      ...
    MYPLUGINPlugin.php (i.e. SmfKarmaPlugin.php)
    plugin.js
    readme.md

CSS and images are currently missing.

PLUGINPlugin.php contains the main plugin class PLUGINPlugin. This class is responsible for all registration stuff that is happening on PHP level.

plugin.js contains the corresponding javascript code.

A plugin can be activated through the configuration:

$config['plugins'] = array(
    'SmfKarma',
);
IngwiePhoenix commented 11 years ago

Looks very much like the plugin/module system in yii. I didnt read the source code; however, to fix your css and images, that should be easy. Eighter provide a global function - or a singleton class - to provide information about the folder structure, or just copy the stuff. An idea would be to have something in the initialization of the chat, like:

$css = scandir($pluginPath."/css");
$css = array_diff($css,array(".",".."));
// do the same for js
foreach($css as $f) { copy($pluginPath."/css/".$f,AJAX_CHAT_ROOT."/css"); }

of course, this doesnt support subdirectories. However, the other way would be to implement my template as PHP patch, and then providing a singleton class that could do: Singleton()->getPluginPath("PLUGINNAME"); The third way is to let the plugins extend from a class:

class AJAXChatPlugin { public $pluginPath; public $pluginName; public function __construct() { /Set the path here/ } }

Now, in any plugin, they can use $this->pluginPath

I hope this helped...and if i was able to use git, i'd put this into your version. It actually is quite easy. :p

Have fun choosing, and a good day. Ingwie Am 19.03.2013 um 15:44 schrieb Borislav Manolov notifications@github.com:

Well, here is my example and very basic implementation of a plugin architecture for AJAX Chat: bmanolov@b27fdab

For now it supports only custom commands but it shouldn't be that hard to introduce some other fancy plugin stuff.

All plugins live under the plugins/ directory and currently have this basic structure:

MYPLUGIN/ (i.e. SmfKarma) lang/ en.php de.php ... MYPLUGINPlugin.php (i.e. SmfKarmaPlugin.php) plugin.js readme.md CSS and images are currently missing.

PLUGINPlugin.php contains the main plugin class PLUGINPlugin. This class is responsible for all registration stuff that is happening on PHP level.

plugin.js contains the corresponding javascript code.

A plugin can be activated through the configuration:

$config['plugins'] = array( 'SmfKarma', ); — Reply to this email directly or view it on GitHub.

bmanolov commented 11 years ago

Yeah, I'd go with just scanning the css/ and js/ plugin folders. Copying files into AJAX_CHAT_ROOT can be skipped though — for now everything lives under the document root, so it is already accessible by the web server.

We just need some use cases to see what is really needed. I'd say that template changes would also be a desired functionality.

IngwiePhoenix commented 11 years ago

Well since you gave this generous idea, I will implement a system similar to my very different version of this software, where many things will be dropped into the plugin system. and the biggest use for that will be the different plattforms - like smf, phpbb and and and ;p Am 19.03.2013 um 17:32 schrieb Borislav Manolov notifications@github.com:

Yeah, I'd go with just scanning the css/ and js/ plugin folders. Copying files into AJAX_CHAT_ROOT can be skipped though — for now everything lives under the document root, so it is already accessible by the web server.

We just need some use cases to see what is really needed. I'd say that template changes would also be a desired functionality.

— Reply to this email directly or view it on GitHub.

IngwiePhoenix commented 11 years ago

I never used SVN or GIT - never XD. I have to get into that some time...but once i do, ill surely shoot all my modifications somewhere for people to look at, integrate, and what not ^^ Am 19.03.2013 um 19:59 schrieb gWorldz notifications@github.com:

Sorry for the off topic but I can't figure out how to message people on GIT ... o.O ... anyway, IngwiePhoenix why can't you use git?

I think I recall that you used subversion on sourceforge when AJAX Chat was primarily hosted there wanted to let you know that git supports SVN now so using something like TortoiseSVN would allow you to use GIT.

https://github.com/blog/626-announcing-svn-support

The tutorial that helped me get started once they first added SVN Support was ...

https://github.com/gegerlan/aog/wiki/Tortoisesvn

... you just need to use the AJAXChat repository instead of AOG's and a final option might be TortoiseGIT ...

http://code.google.com/p/tortoisegit/

... but I've never tried using it. Sorry for all the Tortoise references but its what I use for SVN and GIT so I feel confident recommending it. Getting you on GIT would be nice since I always wanted a closer look at BIRD Edition mods you added to AJAX Chat and getting you to toss it on GIT would be fun :)

Again, bmanolov I apologize for the off topic will try to figure out how to remove this message in a couple days after giving IngwiePhoenix and other random onlookers a chance at the info, just hope its helpful to someone o.O

— Reply to this email directly or view it on GitHub.

Frug commented 11 years ago

I'm not sure I see a need to scan folders when the plugin can specify the files it uses once it's loaded. It's not difficult to create a base plugin class that has functions to inject/include its css and javascript, and call those methods if needed within your plugin. Many plugins won't even have css since the most common requests tend to be things like adding commands. I'm not sure what sort of performance hit is caused by scanning folders, but if it's easy to avoid, I would.

I see how to load plugins is pretty simple, but how about actually injecting the plugin code? How should the javascript be added to existing javascript? How to register their functions? Currently it's done with overrides and hooks.

bmanolov commented 11 years ago

IO stuff like scanning folders or reading files is expensive, I agree. The calls can be cached though, so there is always a room for improvement. First step is “make it work”. :-)

Let us not worry about performance at the beginning as we should concentrate on the big picture and decide what this architecture needs to support. Fine-tuning comes after that. :-)

For extending functionality, we can use hooks and events. If we integrate the jQuery library, we could make a good use of its event functionality. It is not so difficult to make our own though if we decide to go this path.

As I see it, we should get our hands dirty and try to write some plugins to get a feeling what is really needed.

IngwiePhoenix commented 11 years ago

You seem to look everywhere but not straight man :) Let's assume SmfKarma is our plugin, and AJAXChatPlugin is our base class. To do this, you need to integrate a little Patch i have been posting as a message to the forums before - its about changing the AJAXChatFileSystem.php file, to not read&replace the html files, but rather using php's include. this way we can have PHP within the template. An addition to that would now be that the chat grants access to the "upper layer" - customAJAXChat. After that is done, which is easy, we can use all functions from within that class. And, the plugins too. Leaning against the idea behind Yii's CHtml class, we'd need to do something like this:

foreach($config['plugins'] as $plugin) { 
    include AJAX_CHAT_ROOT."/plugins/".$plugin."plugin.php"; 
    $pClass = $plugin."Plugin";
    if(class_exists($pClass)) { 
        $this->plugins[$plugin] = new $pClass;
        $tPlugin = $this->plugins[$plugin];
        if(!isset($tPlugin->cssDir)) $tPlugin->cssDir=AJAX_CHAT_ROOT."/plugin/css";
        if(!isset($tPlugin->jsDir)) $tPlugin->cssDir=AJAX_CHAT_ROOT."/plugin/css";
        if(!isset($tPlugin->css)) { foreach($tPlugin->css as $script) { $this->registeredCss[]=$tPlugin->cssDir."/".$script; } }
        if(!isset($tPlugin->javascript)) { foreach($tPlugin->javascript as $script) { $this->registeredJavascript[]=$tPlugin->jsDir."/".$script; } }        
    }
}

Now, you see we have two arrays - registeredCss and registeredJavascript. Now, we need an additional method in the customajaxchat class:

public function registerScripts() {
    $html = null;
    foreach($this->registeredCss as $css) {
        $html .= '<link rel="stylesheeth" href="'.$css.'" />'.PHP_EOL;
    }
    foreach($this->registeredJavascript as $js) {
        $html .= '<script type="text/javascript" src="'.$js.'"></script>'.PHP_EOL;
    }
    return $html;
}

Now that we set the functions up, and made our templates be able to use PHP, we just need this:

<head>
    <?=$this->registerScripts();?>
    ...
</head>

And you get all your scripts inserted, no problem :)!

Also, its not that big of a performance hit to check for a file's existance o.o... Am 19.03.2013 um 21:24 schrieb Philip Nicolcev notifications@github.com:

I'm not sure I see a need to scan folders when the plugin can specify the files it uses once it's loaded. It's not difficult to create a base plugin class that has functions to inject/include its css and javascript, and call that class if needed within your plugin. I'm not sure what sort of performance hit is caused by scanning folders, but if it's easy to avoid, I would.

I see how to load plugins is pretty simple, but how about actually injecting the plugin code? How should the javascript be added to existing javascript? How to register their functions? Currently it's done with overrides and hooks.

— Reply to this email directly or view it on GitHub.

Frug commented 11 years ago

Yes, but you've missed the problem with simply adding javascript and calling it a plugin. The majority of plugins that affect the javascript in this project override existing methods of the ajaxChat object. As far as I know, you can't simply re-define that extended class multiple times and have it resolve itself.

Adding global functions has always been trivial just like adding some CSS to the top of a page is trivial.

At least half of all mods for ajaxChat require javascript that can't be thrown onto the page like that. Or am I wrong?

I think the exact same problem arises with the php. What can I do with php included into the ajaxChat class? How can I, for example, add another case to ParseCustomCommands to create the away command given here: https://github.com/Frug/AJAX-Chat/wiki/Mod-away-command by simply including some php into the class?

What happens when two plugins want to do the same thing?

Each plugin will have to register a hook, in some sequence, that gets included into the method we want to modify.

bmanolov commented 11 years ago

That's what I'm talking about. :-) We need real stuff to work on.

As a matter of fact, I have implemented the same away command on my chat server (together with an autoaway command), so it will be a good exercise for me to do it with a plugin. I'll put it on my todo list for tomorrow. :-)

IngwiePhoenix commented 11 years ago

add a line into the parse custom commands:

foreach($this->plugins as $plugin) {
    if($plugin,"parseCustomCommands") { $plugin->parseCustomCommands(...); }    
}

You see that you will have to do a validation check if the function returned TRUE or FALSE; althought with some hacking this is quite possible - just store the state every time by catching the current one in a variable or something.

Adding plugins to JavaScript is in fact a little tricky and requires the chat to become a little rewritten in it's frontend so it is familar with the plugins - you could use PHP to display a script block within the page with a js array of the plugin-singletons. I havent coded some real javascript, only tweaked stuff with it; i prefer to stick with PHP and only do the most less stuff with JS as possible ._. However, i am sure there is some nice work-aorund! :3

Also you dont need to re-define a class over and over - not getting what you mean here o.-.o

sometimes the trivial stuff is the most effective, haha.

Am 19.03.2013 um 22:24 schrieb Philip Nicolcev notifications@github.com:

Yes, but you've missed the problem with simply adding javascript and calling it a plugin. The majority of plugins that affect the javascript in this project override existing methods of the ajaxChat object. As far as I know, you can't simply re-define that extended class multiple times and have it resolve itself.

Adding global functions has always been trivial just like adding some CSS to the top of a page is trivial.

At least half of all mods for ajaxChat require javascript that can't be thrown onto the page like that. Or am I wrong?

I think the exact same problem arises with the php. What can I do with php included into the ajaxChat class? How can I, for example, add another case to ParseCustomCommands to create the away command given here: https://github.com/Frug/AJAX-Chat/wiki/Mod-away-command by simply including some php into the class?

— Reply to this email directly or view it on GitHub.

Frug commented 11 years ago

"add a line into the parse custom commands:"

Exactly. You mean add a hook. That's what I've been saying. :smile:

A "plugin" system will require these hooks to be placed anywhere that people may find useful. It is not as simple as adding global functions.

There are other options on the javascript side as well. I believe there are ways to restructure the object so that it accepts a similar form of hooks or module loading, but it requires some research.

Personally I would think the place to start isn't a whole plugins system with hooks and all that, it's finding a way to simplify common tasks. I think adding things like /commands and user fields could be made simpler. In fact, you can add the whole hooks system first and worry about loading plugins later.

@bmanolov : I don't want to add jquery just for this, it can be done with vanilla javascript. Also the away command is a good test to see what's required to add custom commands, but I plan to add the away command to the core chat for the next release, including a new database field for the user's status.

IngwiePhoenix commented 11 years ago

Well we could load commands in a array and take use of the clasure thing... o.o array( '/away'=>function(){...} ); just a very random idea, haha. using array merge, these arrays could be combined. But this is a way to go, actually o.o... I never coded a plugin system i only modded the chat itself in many ways. But well, once we go that done, the chat could simply have plugins for the different plattforms, i belive this would make a few things easyer ^^ Am 19.03.2013 um 22:55 schrieb Philip Nicolcev notifications@github.com:

"add a line into the parse custom commands:"

Exactly. You mean add a hook. That's what I've been saying.

A "plugin" system will require these hooks to be placed anywhere that people may find useful. It is not as simple as adding global functions.

There are other options on the javascript side as well. I believe there are ways to restructure the object so that it accepts a similar form of hooks or module loading, but it requires some research.

Personally I would think the place to start isn't a whole plugins system with hooks and all that, it's finding a way to simplify common tasks. I think adding things like /commands and user fields could be made simpler. In fact, you can add the whole hooks system first and worry about loading plugins later.

@bmanolov : I don't want to add jquery just for this, it can be done with vanilla javascript. Also the away command is a good test to see what's required to add custom commands, but I plan to add the away command to the core chat for the next release, including a new database field for the user's status.

— Reply to this email directly or view it on GitHub.

bmanolov commented 11 years ago

So here it is: https://github.com/bmanolov/AJAX-Chat/commit/e5890a338343395b6014f91e981b1d0e57fa3ff9 - an away command

It is just an example of what is currently possible. If the away command gets into the core, then this plugin can be suspended. :smiley:

As I said, this simple plugin system already supports custom commands so it was no that hard to do it. However we didn't have an event system, so I implemented one. I've also added a possibility to configure a plugin.

The plugin can be activated in config.php:

$config['plugins'] = array(
    'AwayCommand',
);

Or with a configuration:

$config['plugins'] = array(
    'AwayCommand' => array(
        'awayPrefix' => '[',
        'awaySuffix' => ']',
    ),
);

The plugin supports the away and online commands. afk and back act as aliases. There is also an automatic removal of the away status if the user sends a new message. This is accomplished through the event system. Currently, only one event is supported (newMessage) but one can easy add many more if desired.

bmanolov commented 11 years ago

I've given it some thought and decided to remove the expensive directory scanning for the plugin files. Now all css and javascript files should be explicitly defined by every plugin.

https://github.com/bmanolov/AJAX-Chat/commit/aea4b4c1df9f36d692f47a2d847babbd9e5c3089

IngwiePhoenix commented 11 years ago

I didnt look at the upload yet; but are you using a method simialr to the registerScripts() that i pointed out? Am 20.03.2013 um 20:24 schrieb Borislav Manolov notifications@github.com:

I've given it some thought and decided to remove the expensive directory scanning for the plugin files. Now all css and javascript files should be explicitly defined by every plugin.

bmanolov@aea4b4c

— Reply to this email directly or view it on GitHub.

bmanolov commented 11 years ago

but are you using a method simialr to the registerScripts() that i pointed out?

Yes, it's exactly the same logic.