Welcome, Guest. Please login or register.
Did you miss your activation email?
May 26, 2012, 02:45:13 PM

Login with username, password and session length
Search:     Advanced search
Wollen Sie dem WebsiteBaker Team beitreten?
Nähere Informationen finden Sie unter hier und auf unserer neuen Webseite.
155540 Posts in 21712 Topics by 7737 Members
Latest Member: deanmacullam
* Home Help Search Login Register
Pages: [1]   Go Down
Print
Author Topic: Modules: Classes, Variables and Security  (Read 1056 times)
thorn

Offline Offline

Posts: 980


WWW
« on: September 04, 2010, 02:41:12 PM »

Hello,

This is a translation from a series of Articles posted to the German subforum in April 2010, see http://www.websitebaker2.org/forum/index.php/topic,17820.msg117575.html
Thanks to Armin Ipfelkofer for the translation.


Changelog:
25 August 2010 - fixed print_error() and print_success().
Juy 20, 2010 - /modules/admin.php: added reference at "Checks, whether user may edit actual page". Is being checked according to User_ID/Group-Membership.
April 21, 2010 – added missing require_once(WB_PATH.'/framework/class.admin.php'); in examples
April 07, 2010 – correction of  typo in example for $admin->get_permission().
April 05, 2010 – in require(WB_PATH.'/modules/admin.php'); removed remark and further indented single items (just below create instance of class admin).
April 20, 2010 - completed


Hello,

first of all let’s see what the individual calls are supposed to do:


require('../../config.php');
Defines the mandatory constant (WB_PATH, ...) and loads the framework/initialize.php.
This virtually “starts” Website Baker.
Is required in backend and frontend for files which are not loaded by WB, i.e. files which are loaded directly by browser or through forwarding.


if(!defined('WB_PATH')) die('Cannot access this file directly');
Stops when constant "WB_PATH" is not defined.
Prevents execution of file directly via browser (or in some other way).
Is recommended to be placed at the very top of each file to be loaded by WB via include/require.


require(WB_PATH.'/modules/admin.php');
Loads file modules/admin.php. This call is recommended to be used in each page-module, but not in standard files (view.php, modify.php, install.php, uninstall.php, add.php, delete.php).
Admin tools and snippets work differently. They do not require this call.
This file:
  • Creates $page_id from $_GET or $_POST. Forwarding to index.php, if not present.
  • Creates $section_id from $_GET oder $_POST. Forwarding to index.php, if not present (optional, if $section_required is set).
  • Creates $js_back as 'javascript: history.go(-1);'
  • Creates a new instance of class admin:
    • Displays admin menu (Pages | Media | Extensions | ...).
    • Checks whether user is logged in. If not, forwarding to login.
    • Checks whether user is authorized for "pages_modify". If not, forwarding to  index.php.
  • Checks whether user may edit actual page (based on admin_groups/admin_users). If not, forwarding to index.php.
  • Displays templates pages_modify.htt (optional, when $print_info_banner is set).
  • Date of page modification (optional, when $update_when_modified is set)
Has to be used in backend of each file that is not loaded by WB/PHP i.e. which is directly loaded from browser or by forwarding and has to display something. Files in backend which do not have to display anything (e.g. moveup.php) have to be structured in another way.
Normally this call is only used by modules of the type "page" (WYSIWYG, NEWS, ...). Admin tools and snippets work differently and do not require this call.

Please note that this call issues the admin menu! Therefor a call of  $admin->print_footer() at the end of the file is mandatory!
The structure of a file using this call normally looks as follows:
Code: (e.g.: modify_settings.php)
<?php
require('../../config.php');

$section_required TRUE;
require(
WB_PATH.'/modules/admin.php');

// Display form

// Print admin footer
$admin->print_footer();
?>

Files like save_settings.php, which are called out of a form must have the following structure:
Code: (e.g.: save_settings.php)
<?php
require('../../config.php');

$section_required TRUE;
require(
WB_PATH.'/modules/admin.php');

// Read POST-data of form

// Update settings
$database->query("UPDATE ".TABLE_PREFIX."mod_xxx_settings SET ... WHERE section_id = '$section_id'");

// Check if there is a db error, otherwise say successful
if($database->is_error()) {
$admin->print_error($database->get_error(), ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
} else {
$admin->print_success($TEXT['SUCCESS'], ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
}

// Print admin footer
$admin->print_footer();
?>



print_error() and print_success()
Calling print_error() interrupts execution of the script and displays an error message. The original script is no longer executed.
Calling print_success() includes an automatic forwarding which is initiated after a short delay.


require_once(WB_PATH.'/framework/class.admin.php');
$admin = new admin(...);

Creates an instance of the class admin and:
  • Checks whether a user is logged in. If not, forwards to the login page. If the user is logged in: checks whether the user has the necessary authorization. If not: termination  (optional)
  • Checks the backend language. Initiates detour (http://...&Lang=XX) if necessary
  • Displays the admin menu (optional).
This call is only required in backend if require(WB_PATH.'/modules/admin.php'); cannot be used and for files which are not intended to create any output.

For further explanation we have to go into some details:
Prototype: function admin($section_name, $section_permission = 'start', $auto_header = true, $auto_auth = true)
$section_name - string. Allowed values: "pages", "media", "addons", "preferences", "settings", "admintools", "access".
Only required to set the actual entry in admin menu (sets class="current").
$section_permission - string. Allowed values: pages, pages_view, pages_add, pages_add_l0, pages_settings, pages_modify, pages_intro, pages_delete,
media, media_view, media_upload, media_rename, media_delete, media_create,
addons,
modules, modules_view, modules_install, modules_uninstall,
templates, templates_view, templates_install, templates_uninstall,
languages, languages_view, languages_install, languages_uninstall,
settings, settings_basic, settings_advanced,
access,
users, users_view, users_add, users_modify, users_delete,
groups, groups_view, groups_add, groups_modify, groups_delete,
admintools,
start

The exact meaning and usage of the individual strings has to be explained somewhere else.
This is the authorization to be checked for.
$auto_header boolean. Allowed values: TRUE, FALSE.
Defines, whether admin menu is to be displayed (TRUE) or not (FALSE).
If the value is TRUE calling $admin->print_footer() at the end of the file is mandatory!
$auto_auth boolean. Allowed values: TRUE, FALSE.
Defines, whether the user has to be logged in (TRUE or not (FALSE).
If the value is TRUE a user who is not logged in will be forwarded to the login page.
ATTENTION: If $auto_auth is set to  FALSE the authorization will not be checked.

A file called in backend which is not intended to issue any output has to be structured as follows:
Code:
<?php
require('../../config.php');

require_once(
WB_PATH.'/framework/class.admin.php');
$admin = new admin('Pages''pages_modify'FALSETRUE);

// code here

die(header('Location: '.ADMIN_URL.'/pages/modify.php?page_id='.$page_id));
?>


$admin->get_permission(...)
Checks whether the user actually logged in has a certain authorization (system, module or template).
Prototype: function get_permission($name, $type = 'system')
$name - string. Name of the authorization to be checked. For module authorization this is the name of the module, for template authorization this is the name of the template.
$type - string. Valid entries: 'system', 'module' or 'template'.
Code:
if(!$admin->get_permission('pages_modify', 'system')) {
  // User is authorized for 'pages_modify'
}
Code:
if(!$admin->get_permission('admintools', 'system')) {
  // User is authorized for 'admintools'
}
Code:
if(!$admin->get_permission('wysiwyg', 'module')) {
  // User is authorized to use the WYSIWYG module
}



How does this look in practice?
Let’s start with a simple case. What should be the “heading” of a file?

Snippets
Snippets are exclusively executed in frontend. Therefor they are loaded via include().
They should consist of a single file (include.php) which normally contains the declarations of functions only. A simple protection against direct calls is sufficient:
Code: (include.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// snippet code
...
?>

Admin Tools
Admin tools are exclusively executed in backend. They are loaded by a "wrapper" (admin/admintools/tool.php).
An admin tool ideally should consist of a single file (tool.php).
A simple protection against direct calls is sufficient (since the wrapper performs the rest).
Code: (tool.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// admin-tool code
...
?>
For files called via include() or require() the simple protection against direct calls is sufficient.
Code: (File loaded via include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code here
...
?>
For the rare cases where a file is loaded directly via the browser of by a detour the following procedure is required:
Code:
<?php
require('../../config.php');

require_once(
WB_PATH.'/framework/class.admin.php');
$admin = new admin('Admintools''admintools'FALSETRUE);

// code here

?>


normal modules (page-Type)
These modules consist of a series of files. Some of these are called by WB, others however are called directly by the browser or a detour. Some in backend, some in frontend.

For the files called by WB in backend
  • add.php
  • delete.php
  • modify.php
basically the protection against direct calls is sufficient
Code: (add.php, delete.php, modify.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>

For backend files, not called by WB, typically
  • save.php
  • modify_settings.php
  • save_settings.php
  • ...
normally require(WB_PATH.'/modules/admin.php'); should be used:
Code:
<?php
require('../../config.php');

// Include WB admin wrapper script
$section_required TRUE// requires section_id
$update_when_modified TRUE// Tells script to update when this page was last updated
require(WB_PATH.'/modules/admin.php');

// code

// Check if there is a database error, otherwise say successful
if($database->is_error()) {
$admin->print_error($database->get_error(), $js_back);
} else {
$admin->print_success($MESSAGE['PAGES']['SAVED'], ADMIN_URL.'/pages/modify.php?page_id='.$page_id);
}

// Print admin footer
$admin->print_footer();
?>

For files loaded in backend via include() protection against direct calls is sufficient.
Code: (via include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>


For file
  • view.php
which is called in frontend by WB protection against direct calls is sufficient.
Code: (view.php)
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>

For frontend files not being called by WB, typically files like
  • add_comment.php
  • save_comment.php
  • ...
the following is used:
Code:
<?php
require('../../config.php');

// code here

?>
Attention: especially here other methods have to be found to avoid uncontrolled usage (e.g. spam comments) of these files. Using CAPTCHAS or ASP is a possibility.

For files being loaded in frontend via include() protection against direct calls is sufficient.
Code: (via include())
<?php
if(!defined('WB_PATH')) die('Cannot access this file directly');

// code
...
?>


thorn.
« Last Edit: September 04, 2010, 03:29:39 PM by thorn » Logged

thorn

Offline Offline

Posts: 980


WWW
« Reply #1 on: September 04, 2010, 02:47:59 PM »

Changelog:
completed April 02, 2010


Hello,

next part: Determine group membership in frontend. And determine home-folder (because it doesn’t fit anywhere else).

$wb is an instance of class wb, which is automatically availabele in frontend (at least in files being called by WB and files called subsequently by these files via include/require).

In files called by the browser (either by clicking on alink or by forwarding) the instance can be created as follows:
Code:
<?php
require_once(WB_PATH.'/framework/class.wb.php');
$wb = new wb;



$wb->is_authenticated()
Checks whether the actual user is authenticated (registered). Issues TRUE or FALSE.
Code:
<?php
if($wb->is_authenticated()) {
  
// User is registered
}

If the actual user is registerd, the following methods can be used.  
Attention: in case the actual user is not registered, using this methods normally creates an error (at least E_NOTICE).


$wb->get_groups_id()
Submits array with the group-ID’s the user is assigned to, e.g. .: array('3','5','15','4').

$wb->get_groups_name()
Submits array with the group-names the user is assigned to, e.g. .: array('Admins','Leser','Editoren','Tester').
Code:
<?php
if($wb->is_authenticated()) {
  
$groups $wb->get_groups_name();
  if(
in_array('Editoren'$groups)) {
    
// create link "frontend-Eeditor" 
  
}
  if(
in_array('Tester'$groups)) {
    
// createlink "report error" 
  
}
  if(
in_array('Leser'$groups)) {
    
// create link "assess page"  }
else {
  
// create link "assess page anonymously"
}

$wb->get_home_folder()
Submits home directory of actual user in relation to MEDIA_DIRECTORY, e.g.: "/john".

Well, thats it….

EDIT: the function get_group_id() is obsolete and must no longer be used!. Use get_groups_id() instead.


thorn.
« Last Edit: September 04, 2010, 03:22:06 PM by thorn » Logged

thorn

Offline Offline

Posts: 980


WWW
« Reply #2 on: September 04, 2010, 03:18:24 PM »

Changelog:
September 21, 2010 - changed one more $wb->add_slashes()
August 25, 2010 – changed many $wb->add_slashes() to  $wb->strip_slashes() – makes much more sense.
April 19, 2010 – Extended example for strip-tags().
April 14, 2010 - $wb->add_slashes() added to most examples.
April 07, 2010 - Inserted ENT_QUOTES (which I forgot) into example htmlspecialchars().
April 05, 2010 – Extended comment in example preg_quote.
April 05, 2010 - Completed

Hello,

third part: Treatement of GET/POST-data.

Usage of  GET- or POST-data with out any means of precaution represents the main gateway for a series of hazards especially XSS and SQL-injjection. Therefor usage of values from the  GET- or POST-array requires special precaution.

Insertion: $_REQUEST must never be used, since usage of $_REQUEST – depending on server configuration – includes the risk that an aggressor “overwrites” a $_POST value with a foisted  $_GET value or vice versa.
 
Examples for risks resulting from „uncontrolled“ usage of $_GET or $_POST:
Code:
$id = $_GET['user_id'];
$query_user = $database->query("SELECT permissions FROM table WHERE user_id='$id'");
If magic_quotes_gpc is deactivated on server
Code:
http://...php?user_id=-10' OR name='Admin'; --
results in
Code:
SELECT permissions FROM table WHERE user_id='-10' OR name='Admin'; --'
If magic_quotes_gpc is activated this hack is no longer working
Code:
SELECT permissions FROM table WHERE user_id='-10\' OR name=\'Admin\'; --'
(Insertion:This example is really nonsense! People using coding like this in frontend should immediately return their computer!)

But even magic_quotes_gpc does not protect against XSS attacks:
Code:
$class = $_GET['class'];
echo "<p class=\"".$class."\">".$text."</p>";
With activated magic_quotes_gpc
Code:
http://....php?class="><script>alert(String.fromCharCode(39)%2BString.fromCharCode(33)%2BString.fromCharCode(39));</script>
results in
Code:
<p class="\"><script>alert(String.fromCharCode(39)+String.fromCharCode(33)+String.fromCharCode(39));</script>">TEXT</p>


Functions for to be used with mySQL queries

The following methods are available in class wb as well as in class admin.
In backend they are called via $admin->, in frontend via $wb->

$wb->add_slashes($string) – Masks certein characters in $string, i.e. adds a  \ in front of the character(s) if magic_quotes_gpc deactivated on the server. If magic_quotes_gpc is activated, PHP will perform this masking by itself.
The following characters will be masked: ' " \ \x00 (zero-byte)
This function serves to obtain correctly masked strings for all strings from  $_GET or $_POST regardless of the server configuration. As seen above, this masking is only important for database operations.  For HTML output see htmlspecialchars()/strip_slashes().
Input: http://...php?name=o'Reilly
Code:
<?php
$name 
$_GET['name']; // possibly "o'Reilly" oder "o\'Reilly"
$name addslashes($_GET['name']); // ERROR: submits "o\'Reilly" or "o\\\'Reilly"
$name $wb->add_slashes($_GET['name']); // CORRECT: in any case "o\'Reilly"

$wb->strip_slashes($string) - Removes (leading) \ from $string, if  magic_quotes_gpc is activated on server.
This function enables to keep the original string for all $_GET or $_POST strings regardless of the server configuration.
Input: http://...php?name=o'Reilly \text
Code:
<?php
$name 
$_GET['name']; // possibl< "o'Reilly \text" or "o\'Reilly \\text"
$name stripslashes($_GET['name']); // ERROR: submits "o'Reilly text" or "o'Reilly \text"
$name $wb->stip-slashes($_GET['name']); // CORRECT: in any case "o'Reilly \text"


$wb->get_post_escaped($field) – corresponds to $wb->add_slashes($_POST[$field])


Other important functions of PHP:

addslashes($string) - masks $string.
addslashes() is commonly used to mask values to be used in database queries:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']); // creates original string
echo "The name is: ".htmlspecialchars($nameENT_QUOTES);
$name addslashes($name); // maskieren
$query $database->query("SELECT * FROM table WHERE name='$name'");
Usage of addslashes() for this purpose has gained a bad name, however, since an exploit became known. This exploit requires  mySQLs default-charset to be set to Simplified Chinese, but nevertheless one should use  mysql_real_escape_string() instead.

mysql_real_escape_s tring($string) – masks certain characters in $string for usage in  mySQL-Query. It requires an open connection to mySQL.
This function is supposed to be more secure than addslashes(), since it masks more characters  (\x00, \n, \r, \, ', " und \x1a) than addslashes() and it is, in contrast to addslashes(), not vulnerable for multy-byte-character-exploit.
The function is used in the same way as addslashes():
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']); // create original string
echo "Der Name ist: ".htmlspecialchars($nameENT_QUOTES);
$name mysql_real_escape_string($name); // masking
$query $database->query("SELECT * FROM table WHERE name='$name'");

is_numeric($string), intval($string), casting,  ...
A series of functions by means of which values can be checked for their data type or they can be converted into a defined data type.
Code:
<?php
// $user_id must be integer:
if(is_numeric($_GET['user_id'])) {
  
$user_id $_GET['user_id'];
} else {
  
$user_id 0;
}
$query $database->query("SELECT * FROM table WHERE id=$user_id");
Short version:
Code:
<?php
// $user_id must be integer:
$user_id intval($_GET['user_id']);
$query $database->query("SELECT * FROM table WHERE id=$user_id");
FunctionResult
is_numeric("10")TRUE
is_numeric(" 10 ")FALSE
is_numeric("10 text")FALSE
is_numeric("text 10")FALSE
is_numeric("")FALSE
intval("10")10
intval(" 10 ")10
intval("10 text")10
intval("text 10")0
intval("")0
Here you can see why the value 0 for ID’s in a database must never be used.

addcslashes($string, $charlist) – masks all characters listed in $charlist within a $string.
A very rarely used function which, however, is very useful for a certain situation: for  SQL-Queries using LIKE.

The following query is supposed to create a list of all persons, the name of which starts with $name:
Code:
<?php
$name 
mysql_real_escape_string($wb->strip_slashes($_GET['name']));
$query $database->query("SELECT * FROM table WHERE name LIKE '".$name."%');
If the field name is indexed, this query is sufficiently performant.
An aggressor could now start a DOS-attack (Denial of Service) by sending a big number of search requests in the form http://...php?name=%_a to the server. This would create:
Code:
SELECT * FROM table WHERE name LIKE '%_a%'
(Select all names which start with any character, followed by any character, followed ba an a and then end in any form). This would result in a terrible table scan orgy.
How to avoid this:
Code:
<?php
$name 
mysql_real_escape_string($wb->strip_slashes($_GET['name']));
$name addcslashes($name'_%'); // mask % and _
$query $database->query("SELECT * FROM table WHERE name LIKE '".$name."%');
This creates
Code:
SELECT * FROM table WHERE name LIKE '\%\_a%'
(Select all names starting with %, followed by _, followed by an a and then end in any form). The atttack is prevented.



Functions for usage with HTML-output

htmlspecialchars($string, ENT_QUOTES) – converts all & < > ' " into HTML-Entities (&amp;, &lt;, ...).
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name htmlspecialchars($nameENT_QUOTES);
echo 
"Name: <strong>$name</strong>";
Input: Hans<script>alert('Hallo!');</script>
Output: Hans&lt;script&gt;alert(&#039;Hallo!&#039;);&lt;/script&gt;
Not nice but safe!

strip_tags($string, $allowed_tags) – removes all HTML-Tags from $string. Tags listed in  $allowed_tags will not be removed.
Code:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name strip_tags($name);
echo 
"Name: <strong>$name</strong>";
Input: Hans<script>alert('Hallo!');</script>
Output: Hansalert('Hallo!');
Also not nice but safe.

Usage of $allowed_tags, however, is not recommended:
Code:
<?php
$name 
$wb->strip_slashes($_GET['name']);
$name strip_tags($name"<b><strong><i>");
echo 
"Name: <strong>$name</strong>";
Input: Hans <b onmouseover="alert('Hello!')">Hans</b> Mustermann<script>alert('Hallo!');</script>
Output: Hans <b onmouseover="alert('Hello!')">Hans</b> Mustermannalert('Hallo!');

Generally strip_tags() removes everything between < and the next > (or up to the end of the string):
Code:
Code:
<?php
echo strip_tags($wb->strip_slashes($_GET['name']);
Input: This is still valid 1<2. Anything else would be a desaster!
Output: This is still valid 1



Functions to be used in links

urlencode($string) - recodes $string to be usable in an URL.
Certain characters (like / ? &) have to be coded in an URL so they are not interpreted as „control characters“. This function performs the recoding.
Example: The string "R&B" is to be submitted as search value via URL:
Code:
http://www.example.org/wb/search/index.php?search=R&B
will not work properly since & will be interpreted as „control character“ (& marks the beginning of a new value). Instead of search="R&B" this will create search="R" and B="".
This is the correct way:
Code:
<?php
$search 
urlencode("R&B"); // creates "R%26B"
echo "http://www.example.org/wb/search/index.php?search=".$search;
Output:
Code:
http://www.example.org/wb/search/index.php?search=R%26B
Please note that this value will be decoded automatically in the target site! Calling  urldecode() is not requred.
Code:
<?php
$search 
$wb->strip_slashes($_GET('search'));
echo 
$search// Output: R&B
In order to transmit several values, use the individual values by  &amp;. & by itself would also be sufficient, it, however, implements the risk of creating HTML-entities:
Code:
<?php
echo '<a href="http://www.example.org/pages/page.php?row=4&copy=1">link</a>';
creates:
Code:
http://www.example.org/page/page.php?row=4©=1
In case it is sure that the value consist of a number only, urldecode()might be left off.

Complete example:
Code:
<?php
$link 
WB_URL.'/modules/mod/file.php';
$url_query '?';
$url_query .= 'page_id='.$page_id;
$url_query .= '&amp;';
$url_query .= 'name='.urlencode($name);
echo 
'<a href="'.$link.$url_query.'">link</a>';
or more compact:
Code:
<?php
echo '<a href="'.WB_URL.'/modules/mod/file.php?page_id='.$page_id.'&amp;name='.urlencode($name).'">link</a>';



Other functions

preg_quote($string, $limiter) – masks special characters in $string to be used in a regular expression. $limiter is the character to be used as limiter. It will also be masked.
In case a user’s entry or any unknown string is to be used in a regular expression, certain characters (like * and ?) have to be masked.
Code:
<?php
$str 
$wb->strip_slashes($_POST['suchwort']);
$str preg_quote($str'~'); // masks all special character plus ~, which is used as limiter in the next line. 
if(preg_match('~\b'.$str.'~i'$text)) {
  
// Hit
}
Otherwise an error might occur (e.g. searchword ="V8("), or it could even be used for a DOS-attack.



See also: Filter-Extension which may be used since PHP 5.2.


thorn.
« Last Edit: September 21, 2010, 05:57:34 PM by thorn » Logged

snark
Guest
« Reply #3 on: September 05, 2010, 09:29:36 PM »

Wouldn't it be nice (or wise) to have à separate subforum dedicated to module development, filled with tutorials and help topics for people creating new things. Now it is mixed with all regular module topics and questions, I guess it would make lifestyle a bit easier for the coders and developpers.
Logged
crnogorac081
AddOn Development
*
Offline Offline

Posts: 1706



« Reply #4 on: September 05, 2010, 11:28:50 PM »

I agree, in meantime, you can mark it as stickey at least not to fall down on list Smiley
Logged

Wow, I coded something myself: PM Messanger Modul ,Searchbox with suggestions
pcwacht
AddOn Development
*
Offline Offline

Posts: 2858



WWW
« Reply #5 on: September 06, 2010, 11:16:56 AM »

And it would be best to stick to ONE language there allos, so it wouldn't be divided or splattered amongst subforums!!

John
Logged

http://www.ictwacht.nl = Dutch ICT info
http://www.pcwacht.nl = My first
both still work in progress, since years.....
Luisehahne
Board Member
Development Team
*****
Offline Offline

Posts: 3147



WWW
« Reply #6 on: September 06, 2010, 02:01:27 PM »

Wouldn't it be nice (or wise) to have à separate subforum dedicated to module development, filled with tutorials and help topics for people creating new things. Now it is mixed with all regular module topics and questions, I guess it would make lifestyle a bit easier for the coders and developpers.

In Board meetings we already talk about a subforum, so that modules developer get all informations from dev-team, to develope their modules more secure and informations for changes in core.

Dietmar
Logged

We are human beings - and nobody is perfect at all.
PurpleEdge

Offline Offline

Posts: 232



WWW
« Reply #7 on: September 13, 2010, 12:49:22 AM »

In the meantime, can someone please mark this as a "sticky" so it appears at the top, and also move it to the documentation group so it is easier to find?
Logged
Pages: [1]   Go Up
Print
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.16 | SMF © 2011, Simple Machines Valid XHTML 1.0! Valid CSS!