This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.
6346
The Pub / [Archive] Re: Logo Madness
« on September 20th, 2011, 01:30 PM »Benevolent dictatorships are alive and well, and most definitely have a place in the world of software production. Sometimes, the buck has to stop and start outside of committee, and a number of software projects could benefit from such (no names, no pack drill...)
6347
Off-topic / Something kind of crazy
« on September 20th, 2011, 12:32 PM »
Did something crazy over the weekend; Steam were offering all of Paradox's published games (which includes Magicka which I already owned) for a ridiculous price - £74.99 for all the games and all their DLC.
Several of them I'd already looked at and made a note for later that I'd get them, but instead I took the plunge.[1] There is a side benefit to this approach, there are games in there I wouldn't have encountered at all otherwise, and I'm finding it's a nice change of pace to try games out that I wouldn't necessarily have tried.
More than that, it does provide a nice source of inspiration, because it means that I'm seeing how different games handle different things, and while it might not seem obvious at all, I find I do get inspired by how different games approach different concepts, and in the most unlikely of ways.
I mean, I've often considered games[2] to have had the benefit of better UI designers than most application authors,[3] simply because they have to do something more imaginative than simply throwing a load of buttons on the screen, and most of the games I've played have relatively sane UIs to them, and I find that quite inspirational.
I guess it's just nice to do something different to what I'd normally do. Variety is, as they say, the spice of life.[4]
I don't think the offer is running any more, and to be honest I'd be surprised if most people here would bother, since I suspect most people here know what they like and like most people, often don't venture beyond it. I don't, generally, but it's nice to man up and try it occasionally...
Several of them I'd already looked at and made a note for later that I'd get them, but instead I took the plunge.[1] There is a side benefit to this approach, there are games in there I wouldn't have encountered at all otherwise, and I'm finding it's a nice change of pace to try games out that I wouldn't necessarily have tried.
More than that, it does provide a nice source of inspiration, because it means that I'm seeing how different games handle different things, and while it might not seem obvious at all, I find I do get inspired by how different games approach different concepts, and in the most unlikely of ways.
I mean, I've often considered games[2] to have had the benefit of better UI designers than most application authors,[3] simply because they have to do something more imaginative than simply throwing a load of buttons on the screen, and most of the games I've played have relatively sane UIs to them, and I find that quite inspirational.
I guess it's just nice to do something different to what I'd normally do. Variety is, as they say, the spice of life.[4]
I don't think the offer is running any more, and to be honest I'd be surprised if most people here would bother, since I suspect most people here know what they like and like most people, often don't venture beyond it. I don't, generally, but it's nice to man up and try it occasionally...
| 1. | Had I bought the games I was interested in, I'd have paid virtually the same anyway. |
| 2. | Well, *good* games. I've seen my fair share of hard-to-play games just because the UI sucks. |
| 3. | As opposed to, say, artists just throwing pixels at the screen. |
| 4. | When it isn't melange, at least. |
6348
Off-topic / Re: PHP IDE for windows
« on September 20th, 2011, 12:19 PM »
Yes, and Nao was enquiring as to how you were finding it... especially, I think, regarding Windows 8.
6349
Features / Re: New revs - Public comments
« on September 20th, 2011, 11:56 AM »- Maybe I should start by renaming the main layer... Right now it's 'context' because its contents changes according to what page we're in, but technically the sidebar is the same.... What should I use? 'default'? Keep 'context'?
I'm still not sure about the default behavior for $where. I'd like to have more opinions... Especially from all of our mod developers out there!
- we can't "detect" through loadBlock, precisely because the skeleton functions won't let users delete the context layer.
Okay I have this error message, tried to keep it short and usable by both.
Posted: September 20th, 2011, 09:41 AM
Re languages: in theory if the language that a board (or user, for that matter) doesn't exist, it should still be falling back to the forum default.
6350
The Pub / [Archive] Re: Logo Madness
« on September 20th, 2011, 11:46 AM »
I actually like having polls like this. It might be a small detail but it is quite important in its own way, and it means that people do have a say in what comes next.
I like involving the community in things but I've seen what happens when too many people have a vote on important things, so it is with the most kindness and respect that I say that I don't want everyone to have a vote on 'big' things, because if we do that, we'll never get anywhere.
Far better, in the long run, is for us to discuss it - maybe even have the discussion in public - let people comment on it and have their feelings known, then we act on it as we feel best. I'm not about to declare that I know what you want better than you do (because I'm not an Apple rep) but I do think that we have the capacity to make decisions for the software better than doing it by collective polling, by doing what we're doing ;)
I like involving the community in things but I've seen what happens when too many people have a vote on important things, so it is with the most kindness and respect that I say that I don't want everyone to have a vote on 'big' things, because if we do that, we'll never get anywhere.
Far better, in the long run, is for us to discuss it - maybe even have the discussion in public - let people comment on it and have their feelings known, then we act on it as we feel best. I'm not about to declare that I know what you want better than you do (because I'm not an Apple rep) but I do think that we have the capacity to make decisions for the software better than doing it by collective polling, by doing what we're doing ;)
6351
The Pub / Re: Copyrights
« on September 20th, 2011, 09:17 AM »
Yes, they are supposed to under the terms of the BSD.
However it gets a bit vague under where the copyright has to be. (The fact that currently their own code isn't even BSD compliant owing to the fact that it's not the proper BSD licence is another matter entirely.)
The wording of the licence says, and I quote:Quote So, let's parse that first clause because that's the one that really applies to us:
* redistribution of source code
- any source code derived from their source, meaning and any all code originally belonging to SMF
* must retain the above copyright notice
- must retain a statement to the effect of @copyright 2011 Simple Machines, it does not state anywhere that the @author entry must be retained, but the copyright notice
* this list of conditions
- a verbatim copy of the BSD licence text somewhere
* and the following disclaimers.
- the following two clauses, and the big bold text that says no warranty is implied.
Now, they don't include the licence text on every file, they only include a reference in the file itself, under the license tag.
To me, I think that to comply with their attitude, we need to do the following:
* Alter the @copyright in the file headers to read:Quote * Make sure the licence page that we list states that Wedge is derived from SMF 2.0, under the terms of http://www.simplemachines.org/about/smf/license.php and that only original SMF code is BSD, while any code that isn't is covered by the Wedge licence
* Make sure the licence file in the package does the same
Then: we have retained the copyright notice, in both source and binary distributions without the weight of multiple tags that are unnecessary. As per above, we're required to keep the copyright and state the terms of licence, and we are following their example in the latter and holding to the former by letter of their licence.
However it gets a bit vague under where the copyright has to be. (The fact that currently their own code isn't even BSD compliant owing to the fact that it's not the proper BSD licence is another matter entirely.)
The wording of the licence says, and I quote:
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution.
* Neither the names of Simple Machines Forum, Simple Machines, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission.
* redistribution of source code
- any source code derived from their source, meaning and any all code originally belonging to SMF
* must retain the above copyright notice
- must retain a statement to the effect of @copyright 2011 Simple Machines, it does not state anywhere that the @author entry must be retained, but the copyright notice
* this list of conditions
- a verbatim copy of the BSD licence text somewhere
* and the following disclaimers.
- the following two clauses, and the big bold text that says no warranty is implied.
Now, they don't include the licence text on every file, they only include a reference in the file itself, under the license tag.
To me, I think that to comply with their attitude, we need to do the following:
* Alter the @copyright in the file headers to read:
@copyright 2010-2011 Wedgeward, wedge.org, portions copyright 2011 Simple Machines
* Make sure the licence file in the package does the same
Then: we have retained the copyright notice, in both source and binary distributions without the weight of multiple tags that are unnecessary. As per above, we're required to keep the copyright and state the terms of licence, and we are following their example in the latter and holding to the former by letter of their licence.
6352
Off-topic / Re: The Oatmeal
« on September 20th, 2011, 08:50 AM »
Netflix originally just mailed out DVDs to people, but as more people get decent internet, they added that option.
It got more complicated when they started rejigging prices and then splitting into two companies.
It got more complicated when they started rejigging prices and then splitting into two companies.
6353
Off-topic / Re: I am looking for a favour
« on September 20th, 2011, 03:12 AM »Hey! Are you saying that 2 years taking high school Latin class was just for naught?
6354
Plugins / Documentation for plugin authors
« on September 20th, 2011, 03:01 AM »
This is mostly a descriptive post, of the mechanics of the add-on manager. If you're not a programmer, it won't interest you at all, but if you are, particularly if you ever plan on writing an plugin for Wedge, it will be of use to you.
NOTE: Some of the mechanics are not yet implemented, but this should serve as the reference for its mechanics.
Also note: plugin authors are not required to understand the inner workings of ManagePlugins.php, in order to write add-ons.
So, what is a plugin? How do they work?
At its simplest, a plugin is a folder within the /plugins/ folder. Inside that folder is a file named plugin-info.xml, and one or more files that relate to that plugin. Some of them will be code, some of them perhaps images or other resources, but at least one should be code to be loaded and executed at appropriate points in the runtime of Wedge.
This forms the underpinning of Wedge's plugin system: hooks. There are points in the code, given named references, which routines in your plugins can hook onto, and state that they want to be executed when that code is.
For example, there is a hook in the board index code, where the list of boards is prepared. Any function that wants to be called at that point will be called, and the list of boards that the user should see will be made available to those functions, and can be modified there. There are many more hooks, scattered throughout the source of Wedge, and likely more will emerge over time.
At its simplest, a plugin need only contain a single file containing a single function, and the appropriate hook to call. (The hook will not only run the function, but make sure to load the file first)
An example
Here's about the simplest possible plugin that can be written. It contains two files, the plugin-info.xml and a PHP file, demo_plugin.php.
plugin-info.xml looks like this:
Code: [Select]
And demo_plugin.php looks like this:
Code: [Select]
When unpacked, both files live in /plugins/demo_plugin/.
Now, the Plugin Manager will show this as an add-on to be enabled; it'll provide the name and description to the user, with an enable button, to allow them to enable it.
When enabled, the code is made aware that when handling the main menu (wherein there is a hook called menu_items), it should load $plugindir/demo_plugin.php (note the .php is added automatically), and run demo_function().
Exactly what a given hook will make available to a plugin will be hook specific, but more on this once the list of hooks is completed.
That's really the foundation of how to build an add-on for Wedge: the plugin-info.xml states what should be done when, and one or more extra files contains the rest of the functions to run.
Vastly more powerful plugins can be constructed out of this basic framework, and even just with this toolset, a lot is possible, but if I were to leave it just at that stage, I would be remiss in my developer duties.
More advanced hook setup
Firstly, an extract of the <hooks> block from WedgeDesk's installer:
Code: [Select]
I haven't quite finished renaming all the files (though I have done most of them). In this case, when we display a topic, there's a hook called display_buttons, which is for modifying the list of buttons that apply to a given topic (e.g. reply, print page, and SD/WD add one for moving topics to the helpdesk), which is what's happening there. There's no file to load, because I know that Subs-WedgeDesk.php will have already been loaded, and that it contains that function.
Then, we have this odd type of hook: a language hook. Normally, you'll define functions to be called, but in certain cases, you might simply just need to load a language file, which is exactly what the language hooks do. Most importantly, they're in the help pop-up and Who's Online pages, where add-ons very typically need to add their contents.
Note: There is no more Modifications.english.php file to modify. Things you'd normally add to Who's Online there, or to Who.language.php directly, just dump into a new file and call through the language hook.
Then there's this really odd thing: provides. This is for when plugins actually declare their *own* hooks. You may have noticed there isn't a version check, and that's because the design generally doesn't need one.
The vast majority of plugins are expected to use the hooks provided, and in which case, they don't generally have to care what version of Wedge they're running with. All they need to know is if the hook is available or not. If it's not, generally you won't be able to install it (because it checks the list of hooks you want to use, against the list it knows it has)
The point of provided hooks like shd_hook_init is that plugins might want to extend other add-ons. I fully envisage writing WedgeDesk plugins and making use of those hooks to do so.
There is one other thing I haven't mentioned: optional hooks. Simply add optional="yes" to the <function> item, and if the hook isn't available, it won't prevent installation, but it will still set it up as if it were. The point of this is if you create a plugin that can work with another, but doesn't *need* it to be installed.
Settings
A fair number of mods don't need much but they do depend on settings existing and having default values. Well behaved mods will check on use with empty(), and some others[1] will go away and create the settings in the database if they don't already exist.
Well, in the setup you can declare the settings your plugin uses. It also means that when we get on to clean-up and uninstallation, the settings are cleaned up to the user's request automatically.
Code: [Select]
Very simple structure: you just create a <settings> block that contains all the settings you want to use, and then a <setting> per actual setting. Note that it won't create the setting if it already exists in the system, but if it does already exist it will use the stated default. (You do need to state a default value.)
Readmes
Everyone likes readme files. The setup in the Plugin Manager allows you to provide readmes, and do so in a fashion that allows for multiple languages.
Simply add a block like so:
Code: [Select]
As long as you put $plugindir at the front, and the file actually exists, it'll be provided to the user when they click on it.
More specifically, it looks through the <readme> items for languages installed by the user, so if English and French are installed and an plugin supports more, only English and French will be shown, and there will be a little flag icon for them to click on. Remember: this is all pretty much automatically handled on your behalf: you just need to provide the readme files in the add-on and link to them in the plugin-info.xml file.
More hokey magic: scheduled tasks
I don't think this is something that's going to come up that often, but it's convoluted enough to do manually that I wanted to make it easier.
Creating a scheduled task in the system is as simple as adding this block:
Code: [Select]
One task per <task> block, and it should be fairly obvious that again, you're indicating how often the task should run, what function to call and a file to load that contains that function.
(NB: Right now the task won't receive a name in the admin panel properly, I haven't yet quite decided how I want to fix that, but rest assured, I'll provide examples once I figure it out.)
Lastly, database changes
This is the big kahuna. If you've used SMF's $smcFunc['db_create_table'] structure, you'll recognise this because it's mostly the same.
Before those who tell me they hate XML or whatever come in: there's a very good reason it's done this way. It means that when it comes around to removing the plugin, the manager can cleanly deal with it properly, something that didn't always happen where installer scripts were left to their own devices to manage tables.
Another example from WedgeDesk showing what goes on:
Code: [Select]
It's a very shortened example but it gives you pretty much everything you need to know.
So it looks to create {db_prefix}helpdesk_tickets (wedge_helpdesk_tickets, then), and this sample has two columns. It should be fairly clear what's going on here, but notice the lack of 'size' being stated. You can, if you want, state the size, but for numeric types, the size will be calculated for you if you don't state it (for example, mediumint always ends up being mediumint(8) if not stated because that's the range mediumint covers[2])
The range of types is increased too, you can have the named int types (tinyint, smallint, mediumint, int, bigint), plus float types (float, real, double), string types (char, varchar)[3], block string types (text, mediumtext)[4] and bitwise types (set, enum)[5]
The other notable thing here is that you just specify the tables. If the structure is different to what's in the current table (e.g. you're adding new features to a mod), the existing table will get the new/changed columns updated. It will NOT remove any existing columns, however.
It will also add any new indexes that you state, but it will not alter or remove primary or unique indexes. This is to protect the integrity of your data; if you need to do this, you can manage it during the enable script, which I'll get into in a moment.[6]
You'll notice the <scripts> block. None of them are required, but you might find it useful to add them if necessary.
These are scripts which run at the appointed time to make any changes that can't be readily automated, especially if they don't occur on a decent number of plugins, or aren't particularly standardised.
WedgeDesk, for example, uses this functionality to make sure that a department exists after installation, when the table may or may not exist, and the table may or may not have a department created even if the table did already exist.
<enable> runs during the time of an add-on being enabled, <disable> when it is being disabled[7], <remove> for when an add-on is being removed, but that data should be kept, and <remove-clean> for when data should be removed as well.
There are also plans to add functionality for adding individual columns and indexes to tables. For the most part the XML looks the same, just it's contained in the <database> tag rather than in a specific <table> tag (there's a <columns> container at <database> level), and each <column> also states the name it should be in, but otherwise it's the same. Ditto for <indexes>.
Phew, so what actually happens when a plugin is enabled?
(Yes, we're almost done!) Enabling an add-on is reasonably straightforward.
The list of operations that is carried out is as follows:
* Verify that the plugin-info.xml file exists and makes some kind of sense.
* Check that any requirements stated in the file are met, in particular hooks. More on this at the end, because some of it is so rarely going to be needed, I haven't bothered to cover it just yet.
* Look at the contents of the <database> tag, whether there are any changes to make. If any are specified, the order of operations is: new/updating tables first, then new columns, then new indexes.
* Run an enable script if specified.
* Check if any settings are stated, whether they already exist and if not, add them to $modSettings.
* Check if any scheduled tasks are stated, whether they already exist, if not add them, otherwise update them.
Lastly, we handle hooks. The process is fairly complex at this point, so if you're really interested, this is the part to pay attention to.
A plugin will have a $folder and an $id. The folder is the physical folder name it has within the /plugins/ folder, and its id is stated in the plugin-info.xml file, right up there in the <plugin> tag right at the top. The distinction is quite important: the id is under the control of the plugin itself, but the folder might not be.
I've seen all kinds of weird cases where folders get mashed by users, sometimes intentionally, sometimes not. So the folder name is made available to the add-on to make use of, and it's always reference-able from its id, assuming it's enabled.
The exact process that happens inside the manager is as follows:
* an array is built, it contains firstly a key 'id' that contains the plugin's id.
* the remainder of the array is simply the list of hooks it refers to, one item per hook, in the format of function-name|file-to-load|plugin (this last is a literal, and helps the hook code identify that it is an plugin that has to be dealt with rather than a hook used otherwise for integration)
This array is stored, serialized, as $modSettings['plugin_' . $folder], and the $folder will be added to $modSettings['enabled_plugins']. (This is simply a string variable containing the folder names of all enabled add-ons, separated by commas)
Note, very importantly, that hooks are not saved to the master hook references (i.e. $modSettings['registered_hooks']) and that except under very specific circumstances, they should not be, either.
This is because during run-time, specifically after $modSettings is loaded, this is when hooks are initialised, and the contents the plugin_* entries are transferred into the hook references. In case a user does something like directly delete an add-on folder, the intention is that it should safely disable the plugin, instead of breaking everything.
During init, $modSettings['enabled_plugins'] is exploded, and each named folder is examined to check it exists, and that there is an plugin-info.xml file in that folder. If so (and only if so), the hooks are engaged, and from that point on, the plugin is enabled and will be called by the system as and when it has indicated it would need to do so.
Three variables are also made available to the system at this point:
$context['enabled_plugins'] - this is an array of all enabled plugins. Unlike its $modSettings counterpart, the key of the array is the plugin's id, and its value is the folder inside /plugins/, so you can always identify where a given plugin should be from that. (And, by proxy, you can also identify which plugins are available.)
$context['plugins_dir'] - another key/value array, this time the key is the plugin's id, and its value is the physical resolved path to that plugin's folder, e.g. /home/myuser/wedge/plugis/myplugin. This is needed so that source files, templates and language files can be loaded.
$context['plugins_url'] - another key/value array, much like plugins_dir, but the URL counterpart. This will allow you to reference your plugin for images in HTML.
Now I have my add-on, what happens when I want to do big stuff with it?
While, yes, small plugins might only be a single small, self contained file, it's highly likely that you're going to want to do bigger plugins sometime, and that means you're going to need to integrate other files, like loading other source files from the plugin directory, or templates, or language files.
It just so happens, hah, that there are functions designed just for this purpose, to make it easier to handle loading things and to abstract away a lot of the underlying mechanics.
I have mentioned these before, but no matter, let's cover them again.
loadPluginSource($plugin_name, $source_name)
- takes the plugin's id and the file within to load, relative to the base of the plugin itself. If you have an add-on that doesn't have subfolders, just use the file's base name, e.g. MyPluginFile (the .php will be added, as will the rest of the path), as this is analogous to how loadSource works in the rest of Wedge.[8]
loadPluginTemplate($plugin_name, $template_name, $fatal = true)
- much like loadTemplate generally, you specify the plugin's id, the template name (without .template.php) and whether it should fatal-error if the file couldn't be found. Much like loadPluginSource, specify a path relative to the plugin's folder if you need it, or not if you don't. If you keep templates in a subfolder, like tpl/, then just use tpl/PluginTemplate, or whatever you're using.
loadPluginLanguage($plugin_name, $template_name, $lang = '', $fatal = true, $force_reload = false)
- used for loading language files. You probably get the idea how to call this by now, and in all respects other than the fact it uses the plugin's directory, it works much as loadLanguage does: you specify the language you want to use and if that's not English, it'll attempt to load that language, falling back on English if necessary.
What about CSS and JavaScript?
While I mentioned $context['plugins_url'] for images, it's generally recommended to *not* use that to load CSS and JavaScript for add-ons. Remember: Wedge has minifiers and similar tools built in that help save bandwidth for both CSS and JavaScript, and really these should be used in preference to adding it manually.
There is a pair of functions for just this purpose, which you can call from your own code.
Code: [Select]
The structure should be fairly obvious - like everything else, it requires the plugin's id, and the file relative to the plugin's own folder. (WedgeDesk has a css/ and js/ folder)
Notice also that the JS is the only one that actually uses a .js extension, everything else does not. In case you're wondering, the 'true' is a reference to add_css_file, and it means whether or not to include the cached CSS file into the header or not, if true, add it to the header and deal with it automatically (recommended for add-ons), false means to simply return the URL to it.
Anything else of interest?
There is also one last item of general interest in the specification: <acp-url>. In there, you simply put in the part of the URL after index.php? that your add-on's settings live in, e.g. <acp-url>action=admin;area=wedgedesk_info</acp-url>. That way, if your add-on has settings, you can direct users to it, and if not (and not all will), you can leave it omitted.
I think that's pretty much everything about how it behaves.
Hang on, you said about file edits.
I did originally think about adding them, but in the end, I came to the conclusion that it's just too unreliable to allow and that while it might hinder pushing for that very peak of performance, and it does cut out some functionality possibilities, it makes life safer for everyone else, and that is more important to me.
Right now, at least, that really is everything!
I forgot to mention a couple of things. There are a few refinements in the error log system and in the language editor.
Error logging
Since we have a list of all the folders that plugins live in, and we know the file an error occurs in, we can check to see if that file is in one of the plugin folders we know we have - so if an add-on causes an error, it's normally possible to trace it back to that plugin.
Language editor
All the language files (at least, anything matching *.english.php when using English, and similarly for other languages) in a given plugin are shown in the language editor underneath the normal list of strings in themes, so you can edit the language strings for any plugin you have installed.
NOTE: Some of the mechanics are not yet implemented, but this should serve as the reference for its mechanics.
Also note: plugin authors are not required to understand the inner workings of ManagePlugins.php, in order to write add-ons.
So, what is a plugin? How do they work?
At its simplest, a plugin is a folder within the /plugins/ folder. Inside that folder is a file named plugin-info.xml, and one or more files that relate to that plugin. Some of them will be code, some of them perhaps images or other resources, but at least one should be code to be loaded and executed at appropriate points in the runtime of Wedge.
This forms the underpinning of Wedge's plugin system: hooks. There are points in the code, given named references, which routines in your plugins can hook onto, and state that they want to be executed when that code is.
For example, there is a hook in the board index code, where the list of boards is prepared. Any function that wants to be called at that point will be called, and the list of boards that the user should see will be made available to those functions, and can be modified there. There are many more hooks, scattered throughout the source of Wedge, and likely more will emerge over time.
At its simplest, a plugin need only contain a single file containing a single function, and the appropriate hook to call. (The hook will not only run the function, but make sure to load the file first)
An example
Here's about the simplest possible plugin that can be written. It contains two files, the plugin-info.xml and a PHP file, demo_plugin.php.
plugin-info.xml looks like this:
<?xml version="1.0" standalone="yes" ?>
<plugin id="Arantor:DemoPlugin">
<name>Demo Plugin</name>
<author>Arantor</author>
<description>Changes the "Home" button on the menu to read "My Home Page"</description>
<version>1.0</version>
<hooks>
<function point="menu_items" function="demo_function" filename="$plugindir/demo_plugin" />
</hooks>
</plugin>And demo_plugin.php looks like this:
<?php
function demo_function(&$items)
{
$items['home']['title'] = 'My Home Page';
}
?>When unpacked, both files live in /plugins/demo_plugin/.
Now, the Plugin Manager will show this as an add-on to be enabled; it'll provide the name and description to the user, with an enable button, to allow them to enable it.
When enabled, the code is made aware that when handling the main menu (wherein there is a hook called menu_items), it should load $plugindir/demo_plugin.php (note the .php is added automatically), and run demo_function().
Exactly what a given hook will make available to a plugin will be hook specific, but more on this once the list of hooks is completed.
That's really the foundation of how to build an add-on for Wedge: the plugin-info.xml states what should be done when, and one or more extra files contains the rest of the functions to run.
Vastly more powerful plugins can be constructed out of this basic framework, and even just with this toolset, a lot is possible, but if I were to leave it just at that stage, I would be remiss in my developer duties.
More advanced hook setup
Firstly, an extract of the <hooks> block from WedgeDesk's installer:
<hooks>
<function point="display_buttons" function="shd_display_btn_mvtopic" />
<language point="lang_who" filename="$plugindir/lang/SimpleDeskWho" />
<provides>
<hook type="function">shd_hook_init</hook>
</provides>
</hooks>I haven't quite finished renaming all the files (though I have done most of them). In this case, when we display a topic, there's a hook called display_buttons, which is for modifying the list of buttons that apply to a given topic (e.g. reply, print page, and SD/WD add one for moving topics to the helpdesk), which is what's happening there. There's no file to load, because I know that Subs-WedgeDesk.php will have already been loaded, and that it contains that function.
Then, we have this odd type of hook: a language hook. Normally, you'll define functions to be called, but in certain cases, you might simply just need to load a language file, which is exactly what the language hooks do. Most importantly, they're in the help pop-up and Who's Online pages, where add-ons very typically need to add their contents.
Note: There is no more Modifications.english.php file to modify. Things you'd normally add to Who's Online there, or to Who.language.php directly, just dump into a new file and call through the language hook.
Then there's this really odd thing: provides. This is for when plugins actually declare their *own* hooks. You may have noticed there isn't a version check, and that's because the design generally doesn't need one.
The vast majority of plugins are expected to use the hooks provided, and in which case, they don't generally have to care what version of Wedge they're running with. All they need to know is if the hook is available or not. If it's not, generally you won't be able to install it (because it checks the list of hooks you want to use, against the list it knows it has)
The point of provided hooks like shd_hook_init is that plugins might want to extend other add-ons. I fully envisage writing WedgeDesk plugins and making use of those hooks to do so.
There is one other thing I haven't mentioned: optional hooks. Simply add optional="yes" to the <function> item, and if the hook isn't available, it won't prevent installation, but it will still set it up as if it were. The point of this is if you create a plugin that can work with another, but doesn't *need* it to be installed.
Settings
A fair number of mods don't need much but they do depend on settings existing and having default values. Well behaved mods will check on use with empty(), and some others[1] will go away and create the settings in the database if they don't already exist.
Well, in the setup you can declare the settings your plugin uses. It also means that when we get on to clean-up and uninstallation, the settings are cleaned up to the user's request automatically.
<settings>
<setting name="shd_new_search_index" default="0" />
</settings>Very simple structure: you just create a <settings> block that contains all the settings you want to use, and then a <setting> per actual setting. Note that it won't create the setting if it already exists in the system, but if it does already exist it will use the stated default. (You do need to state a default value.)
Readmes
Everyone likes readme files. The setup in the Plugin Manager allows you to provide readmes, and do so in a fashion that allows for multiple languages.
Simply add a block like so:
<readmes>
<readme lang="english">$plugindir/readme/readme.english.txt</readme>
</readmes>As long as you put $plugindir at the front, and the file actually exists, it'll be provided to the user when they click on it.
More specifically, it looks through the <readme> items for languages installed by the user, so if English and French are installed and an plugin supports more, only English and French will be shown, and there will be a little flag icon for them to click on. Remember: this is all pretty much automatically handled on your behalf: you just need to provide the readme files in the add-on and link to them in the plugin-info.xml file.
More hokey magic: scheduled tasks
I don't think this is something that's going to come up that often, but it's convoluted enough to do manually that I wanted to make it easier.
Creating a scheduled task in the system is as simple as adding this block:
<scheduledtasks>
<task runevery="1" runfreq="day" name="shd_scheduled" file="$plugindir/src/WedgeDesk-Scheduled" />
</scheduledtasks>One task per <task> block, and it should be fairly obvious that again, you're indicating how often the task should run, what function to call and a file to load that contains that function.
(NB: Right now the task won't receive a name in the admin panel properly, I haven't yet quite decided how I want to fix that, but rest assured, I'll provide examples once I figure it out.)
Lastly, database changes
This is the big kahuna. If you've used SMF's $smcFunc['db_create_table'] structure, you'll recognise this because it's mostly the same.
Before those who tell me they hate XML or whatever come in: there's a very good reason it's done this way. It means that when it comes around to removing the plugin, the manager can cleanly deal with it properly, something that didn't always happen where installer scripts were left to their own devices to manage tables.
Another example from WedgeDesk showing what goes on:
<database>
<tables>
<table if-exists="update" name="{db_prefix}helpdesk_tickets">
<columns>
<column name="id_ticket" type="mediumint" autoincrement="yes" unsigned="yes" />
<column name="id_dept" type="smallint" unsigned="yes" />
</columns>
<index type="primary">
<field>id_ticket</field>
</index>
</table>
</tables>
<scripts>
<enable>$plugindir/enable.php</enable>
<disable>$plugindir/disable.php</disable>
<remove>$plugindir/remove.php</remove>
<remove-clean>$plugindir/removeclean.php</remove-clean>
</scripts>
</database>It's a very shortened example but it gives you pretty much everything you need to know.
So it looks to create {db_prefix}helpdesk_tickets (wedge_helpdesk_tickets, then), and this sample has two columns. It should be fairly clear what's going on here, but notice the lack of 'size' being stated. You can, if you want, state the size, but for numeric types, the size will be calculated for you if you don't state it (for example, mediumint always ends up being mediumint(8) if not stated because that's the range mediumint covers[2])
The range of types is increased too, you can have the named int types (tinyint, smallint, mediumint, int, bigint), plus float types (float, real, double), string types (char, varchar)[3], block string types (text, mediumtext)[4] and bitwise types (set, enum)[5]
The other notable thing here is that you just specify the tables. If the structure is different to what's in the current table (e.g. you're adding new features to a mod), the existing table will get the new/changed columns updated. It will NOT remove any existing columns, however.
It will also add any new indexes that you state, but it will not alter or remove primary or unique indexes. This is to protect the integrity of your data; if you need to do this, you can manage it during the enable script, which I'll get into in a moment.[6]
You'll notice the <scripts> block. None of them are required, but you might find it useful to add them if necessary.
These are scripts which run at the appointed time to make any changes that can't be readily automated, especially if they don't occur on a decent number of plugins, or aren't particularly standardised.
WedgeDesk, for example, uses this functionality to make sure that a department exists after installation, when the table may or may not exist, and the table may or may not have a department created even if the table did already exist.
<enable> runs during the time of an add-on being enabled, <disable> when it is being disabled[7], <remove> for when an add-on is being removed, but that data should be kept, and <remove-clean> for when data should be removed as well.
There are also plans to add functionality for adding individual columns and indexes to tables. For the most part the XML looks the same, just it's contained in the <database> tag rather than in a specific <table> tag (there's a <columns> container at <database> level), and each <column> also states the name it should be in, but otherwise it's the same. Ditto for <indexes>.
Phew, so what actually happens when a plugin is enabled?
(Yes, we're almost done!) Enabling an add-on is reasonably straightforward.
The list of operations that is carried out is as follows:
* Verify that the plugin-info.xml file exists and makes some kind of sense.
* Check that any requirements stated in the file are met, in particular hooks. More on this at the end, because some of it is so rarely going to be needed, I haven't bothered to cover it just yet.
* Look at the contents of the <database> tag, whether there are any changes to make. If any are specified, the order of operations is: new/updating tables first, then new columns, then new indexes.
* Run an enable script if specified.
* Check if any settings are stated, whether they already exist and if not, add them to $modSettings.
* Check if any scheduled tasks are stated, whether they already exist, if not add them, otherwise update them.
Lastly, we handle hooks. The process is fairly complex at this point, so if you're really interested, this is the part to pay attention to.
A plugin will have a $folder and an $id. The folder is the physical folder name it has within the /plugins/ folder, and its id is stated in the plugin-info.xml file, right up there in the <plugin> tag right at the top. The distinction is quite important: the id is under the control of the plugin itself, but the folder might not be.
I've seen all kinds of weird cases where folders get mashed by users, sometimes intentionally, sometimes not. So the folder name is made available to the add-on to make use of, and it's always reference-able from its id, assuming it's enabled.
The exact process that happens inside the manager is as follows:
* an array is built, it contains firstly a key 'id' that contains the plugin's id.
* the remainder of the array is simply the list of hooks it refers to, one item per hook, in the format of function-name|file-to-load|plugin (this last is a literal, and helps the hook code identify that it is an plugin that has to be dealt with rather than a hook used otherwise for integration)
This array is stored, serialized, as $modSettings['plugin_' . $folder], and the $folder will be added to $modSettings['enabled_plugins']. (This is simply a string variable containing the folder names of all enabled add-ons, separated by commas)
Note, very importantly, that hooks are not saved to the master hook references (i.e. $modSettings['registered_hooks']) and that except under very specific circumstances, they should not be, either.
This is because during run-time, specifically after $modSettings is loaded, this is when hooks are initialised, and the contents the plugin_* entries are transferred into the hook references. In case a user does something like directly delete an add-on folder, the intention is that it should safely disable the plugin, instead of breaking everything.
During init, $modSettings['enabled_plugins'] is exploded, and each named folder is examined to check it exists, and that there is an plugin-info.xml file in that folder. If so (and only if so), the hooks are engaged, and from that point on, the plugin is enabled and will be called by the system as and when it has indicated it would need to do so.
Three variables are also made available to the system at this point:
$context['enabled_plugins'] - this is an array of all enabled plugins. Unlike its $modSettings counterpart, the key of the array is the plugin's id, and its value is the folder inside /plugins/, so you can always identify where a given plugin should be from that. (And, by proxy, you can also identify which plugins are available.)
$context['plugins_dir'] - another key/value array, this time the key is the plugin's id, and its value is the physical resolved path to that plugin's folder, e.g. /home/myuser/wedge/plugis/myplugin. This is needed so that source files, templates and language files can be loaded.
$context['plugins_url'] - another key/value array, much like plugins_dir, but the URL counterpart. This will allow you to reference your plugin for images in HTML.
Now I have my add-on, what happens when I want to do big stuff with it?
While, yes, small plugins might only be a single small, self contained file, it's highly likely that you're going to want to do bigger plugins sometime, and that means you're going to need to integrate other files, like loading other source files from the plugin directory, or templates, or language files.
It just so happens, hah, that there are functions designed just for this purpose, to make it easier to handle loading things and to abstract away a lot of the underlying mechanics.
I have mentioned these before, but no matter, let's cover them again.
loadPluginSource($plugin_name, $source_name)
- takes the plugin's id and the file within to load, relative to the base of the plugin itself. If you have an add-on that doesn't have subfolders, just use the file's base name, e.g. MyPluginFile (the .php will be added, as will the rest of the path), as this is analogous to how loadSource works in the rest of Wedge.[8]
loadPluginTemplate($plugin_name, $template_name, $fatal = true)
- much like loadTemplate generally, you specify the plugin's id, the template name (without .template.php) and whether it should fatal-error if the file couldn't be found. Much like loadPluginSource, specify a path relative to the plugin's folder if you need it, or not if you don't. If you keep templates in a subfolder, like tpl/, then just use tpl/PluginTemplate, or whatever you're using.
loadPluginLanguage($plugin_name, $template_name, $lang = '', $fatal = true, $force_reload = false)
- used for loading language files. You probably get the idea how to call this by now, and in all respects other than the fact it uses the plugin's directory, it works much as loadLanguage does: you specify the language you want to use and if that's not English, it'll attempt to load that language, falling back on English if necessary.
What about CSS and JavaScript?
While I mentioned $context['plugins_url'] for images, it's generally recommended to *not* use that to load CSS and JavaScript for add-ons. Remember: Wedge has minifiers and similar tools built in that help save bandwidth for both CSS and JavaScript, and really these should be used in preference to adding it manually.
There is a pair of functions for just this purpose, which you can call from your own code.
add_plugin_css_file('Arantor:WedgeDesk', 'css/helpdesk', true);
add_plugin_js_file('Arantor:WedgeDesk', 'js/helpdesk.js');The structure should be fairly obvious - like everything else, it requires the plugin's id, and the file relative to the plugin's own folder. (WedgeDesk has a css/ and js/ folder)
Notice also that the JS is the only one that actually uses a .js extension, everything else does not. In case you're wondering, the 'true' is a reference to add_css_file, and it means whether or not to include the cached CSS file into the header or not, if true, add it to the header and deal with it automatically (recommended for add-ons), false means to simply return the URL to it.
Anything else of interest?
There is also one last item of general interest in the specification: <acp-url>. In there, you simply put in the part of the URL after index.php? that your add-on's settings live in, e.g. <acp-url>action=admin;area=wedgedesk_info</acp-url>. That way, if your add-on has settings, you can direct users to it, and if not (and not all will), you can leave it omitted.
I think that's pretty much everything about how it behaves.
Hang on, you said about file edits.
I did originally think about adding them, but in the end, I came to the conclusion that it's just too unreliable to allow and that while it might hinder pushing for that very peak of performance, and it does cut out some functionality possibilities, it makes life safer for everyone else, and that is more important to me.
Right now, at least, that really is everything!
Posted: September 20th, 2011, 02:45 AM
I forgot to mention a couple of things. There are a few refinements in the error log system and in the language editor.
Error logging
Since we have a list of all the folders that plugins live in, and we know the file an error occurs in, we can check to see if that file is in one of the plugin folders we know we have - so if an add-on causes an error, it's normally possible to trace it back to that plugin.
Language editor
All the language files (at least, anything matching *.english.php when using English, and similarly for other languages) in a given plugin are shown in the language editor underneath the normal list of strings in themes, so you can edit the language strings for any plugin you have installed.
| 1. | Depending on your perspective, this may be less or more well behaved. |
| 2. | 0 to 16777215 (8 digits for unsigned) vs -8388608 (8 digits for signed) to 8388607, if you're wondering. The others are all worked out for you, for tinyint, smallint, mediumint, int and bigint. |
| 3. | If size isn't given, it defaults to 50 characters. |
| 4. | Naturally, no size is applicable for these, and it won't let you set a default for them either. |
| 5. | You need to add a values attribute, e.g. values="'1','2','3'", with the values as single quoted with commas. It's not a big ask, seeing how it's the same that phpMyAdmin asks of you, and it's not like you're going to do it all that often. |
| 6. | This is a functional improvement over SMF; 2.0 RC3 featured the ability to add new columns to existing tables, just off the create_table call, but didn't touch indexes, by the time final shipped, this was removed, so mod authors were expected to manage any changes themselves as opposed to SMF doing it for them. I not only reinstated the ability to make such changes, I added index changes, and made it possible for the schema changes to be carried out in a single ALTER TABLE statement for each table, rather than one ALTER TABLE per operation that needed to be done. Thus the total number of file operations that involve creating one or more duplicates of the table is limited to one per table. |
| 7. | Think of 'uninstall' in relation to SMF mods, and you have the right idea, but it is typically a disable here rather than undoing code edits. |
| 8. | You shouldn't, generally, need to reference $sourcedir or $pluginsdir manually, which is why loadSource and loadPluginSource even exist. |
6355
Features / Re: New revs - Public comments
« on September 19th, 2011, 05:47 PM »So I turned them into inline-blocks and it works, but then I remembered that inline-blocks take whitespace into account, ah ah... Well, there are hacks to prevent that anyway.
Re: layers, well, do you think I should set $where to a different default depending on the target layer...? $target = context, $where = replace by default. $target != context, $where = add by default. It would allow us to remove the $where param from pretty much all loadBlock calls, but it's really not exactly 'obvious'.
Oh, and I think I have a couple of questions/concerns in the New Revs topic that you didn't comment on. If you don't mind.
@ Need to write a multi-language error for the context layer error... Would you care to write it, Pete? I lack inspiration to do something that can be understood by normal users and not overly dramatic at the same time
Assuming that it's only ever add-ons that will cause that... show this to users.
"Template Skeleton Error
Wedge maintains what's called a template skeleton, to set out the page structure. Unfortunately, it appears that an add-on you have installed has damaged this skeleton, and the page cannot continue. The administrator has been made aware."[1]
Alternatively for administrators:
"Template Skeleton Error
Wedge maintains what's called a template skeleton, to set out the page structure. Unfortunately, it appears that an add-on you have installed has damaged this skeleton, and the page cannot continue. You should contact the author of the add-on you installed and were trying to use at the time this error occurred."
It's vague but unless we can tie it down to a given add-on, that's really about the best we can do, unless you want to log the backtrace of calls made to loadBlock to see the file that the place the call came from (which will give you a path which will, by extension, identify any add-on that isn't using file edits)
| 1. | Because it should be logged, assuming you didn't pass false as the second parameter to fatal_lang_error, haven't checked. Ideally it should be 'template' here. |
6356
Features / Re: Standardization of UI elements
« on September 19th, 2011, 12:32 PM »I like that, it's quite straight forward and individual elements make sense. It's much better than what I currently have.
But what about post-like elements? There are a ton of them, and all of them follow the same layout more or less.
6357
Features / Re: Standardization of UI elements
« on September 19th, 2011, 12:21 PM »Forms is one of my biggest quirks, I don't know how Zend_Form does it but any way is better than coding those form tables again and again and then validating them.
That was something I put in from top of my head, but, search, report viewing, comment section of many mods etc.
6358
Features / Re: Standardization of UI elements
« on September 19th, 2011, 12:07 PM »
Not going to get into the others just yet but form handling is quite important to do and get right; there has been mention before of a centralised form handler that can do both rendering and validation, and that would be good to implement (essentially like what the prepareDBSettingsContext bit does in the admin area, as far as creating and validating the general settings forms.
Part of the reason why it's not gone any further is because we've been busy elsewhere, and partly because I don't think we can really agree on a structure for it to follow. Part of me wants to follow the general model that Zend_Form does, but the rest of me wants to run the hell away from it...
As far as lists go, there's the generic list subsystem that's used for simpler lists, whereas the message index and board index don't really fit into that category; and pushing them through a generic handler reduces the ease and range of customisation that occurs - remember that most custom themes that do use their own templates invariably hit BoardIndex, MessageIndex and Display in that order. I see that being just as much of an issue in Wedge, and forcing them to be standardised (as opposed to unique but stylistically consistent) would make it worse, not better.
As for controls, other than the topic display, where are they even used?
Part of the reason why it's not gone any further is because we've been busy elsewhere, and partly because I don't think we can really agree on a structure for it to follow. Part of me wants to follow the general model that Zend_Form does, but the rest of me wants to run the hell away from it...
As far as lists go, there's the generic list subsystem that's used for simpler lists, whereas the message index and board index don't really fit into that category; and pushing them through a generic handler reduces the ease and range of customisation that occurs - remember that most custom themes that do use their own templates invariably hit BoardIndex, MessageIndex and Display in that order. I see that being just as much of an issue in Wedge, and forcing them to be standardised (as opposed to unique but stylistically consistent) would make it worse, not better.
As for controls, other than the topic display, where are they even used?
6359
Features / Re: New revs - Public comments
« on September 19th, 2011, 11:08 AM »Re: cat, I suppose nobody has an opinion on this... Not even us
Re: add-on manager, most likely at the end of the process?
Re: layers, yes indeed, I forgot to add hidden chromes to the list of exceptions.
Re: I did a lot of tweaking for the menu padding before, and, err... What can I say -- 6px is a bit much for me.
6360
Off-topic / Re: PC hotline
« on September 19th, 2011, 11:04 AM »How do you keep track of your code search history for later investigations?
How do you keep track of your logo history, do you save every single version of the logo after you made a change? Oh no you're not dealing with the logo
I don't know that it is W7 doing what I think it is. I don't know that it isn't. What I know for certain is that the same behaviour has been exhibited before on earlier Windows versions for that reason.
Also, be sure to sweep for nasties, just in case.