CTLModuleMaker FAQ

Covers the created modules, not CTLModuleMaker itself...
Modifying the module - where to start?

To get you started, here's a basic explanation of the structure of the created module.

The language files

In the lang/ folder, you will find one file for each supported language. Inside each file, you should have several lines that look like this:

$lang["postinstall"] = "Module successfully added.";

This line defines a string referred to as "postinstall". In every language file, there should be a string for this key. When the modules wishes to display some text, it will look for the string corresponding to the key in the language file corresponding to the user's language. You will most likely wish to change these files, but be careful. Each string is delimited with quotes (either ' or "), and obviously you can't use these delimiters inside the string. If you wish to do so, you'll have to use an escape sign : \" will display a quote without being considered as a delimiter.

The templates

The files in the templates/ folder contain no php - just plain html and smarty tags. They are used for the layout of most actions.

adminpanel.tpl is the layout for the list of elements of each level in the admin panel (to modify this, see this topic)
noresult.tpl is used when there are no item to display. Typically, this is just a message saying that nothing was found.
search.tpl is the template for the simple search form (when calling the search action with searchmode="simple"), which searches in all levels.
search_generalresults.tpl is the template for the search results when you are searching in more than one level (otherwise, the default list template for that level is used).
browsefiles.tpl is the template used for the file selection page (when using table display).
For each level, you should have a editLEVELPREFIX.tpl, a search_LEVELNAME.tpl and possibly a frontend_add_LEVELPREFIX.tpl. The first is the template for the admin form, when an item of this level is added or edited. The second is the template for the advanced search form. The last is the template for the frontend add action for this level (if you chose to create one).

The action files

In the root of the module folders, you will notice that files have a prefix (either "action" or "method"), except for the module file (modulename.module.php), which contains the most important parts of the code. Methods are called when the module is installed, uninstalled, or upgraded - and at no other moment. Actions, on the other hand, are called everytime the cms asks the module to do something - either in the frontend or the admin. But the module has more actions than the files you see there. Open the module file (modulename.module.php), and look for the function DoAction (should be around the 400th line). You should see something like this:

	function DoAction($action, $id, $params, $returnid=-1){
global $gCms;
switch($action){
case "link":
echo $this->CreateLink($id,"default",$returnid,"",$params,"",true);
break;
case "changedeftemplates":
foreach($params as $key=>$value){
if($key != "submit") $this->setPreference($key, $value);
}
$this->Redirect($id, "defaultadmin", $returnid, array("active_tab"=>"templates", "module_message"=>"message_modified"));
break;
case "deletetpl":
$newparams = array("active_tab"=>"templates");
$deftemplates = $this->getDefaultTemplates();
if(isset($params["tplname"]) && !in_array($params["tplname"], $deftemplates)){
if($this->DeleteTemplate($params["tplname"])) $newparams["module_message>"] = $this->lang("message_modified");
}
$this->Redirect($id, "defaultadmin", $returnid, $newparams);
break;

and so on...
Some actions (mostly because they are small) don't have their own file. Each "case", here, is such an action. When asked for an action that is not in the list, the module will look for the according file (action.ACTIONNAME.php).

So here's a short description of the actions you might notice in your module:

  • link: the frontend "link" action, which creates a link to another action
  • default: the frontend default action, which displays elements or list of elements
  • search: the frontend "search" action, which displays the search form and search results
  • frontend_edit: the frontend action to add/edit elements (breaks into FEadd_* actions)
  • defaultadmin: the action for the admin panel which displays the list of levels, elements, templates and so on
  • editTemplate: when a module template is edited
  • deletetpl: when a module template is deleted
  • add_optionvalue,rename_optionvalue,move_optionvalue and delete_optionvalue manage the options for the dynamic list fields
  • editLEVELPREFIX: for each level, the action to add or edit an element of the level
  • toggle: used when a link in the adminpanel is clicked to change the "active" or "default" value of an element
  • movesomething: used when the order of elements is changed in the admin panel, or when an element is deleted
  • browsefiles: used when you select a file for an element. It displays a list of the current files and the upload form.
  • addfiles: used when new file(s) is(are) uploaded. This action is called from the browsefiles action (images are resized here).
  • assignfile: assigns an existing file to the item in the database

The functions

Aside from DoAction, the module file contains a lot of functions that are used in any action. Most of them are simply procedures that the module needs to do quite often. I've tried to comment them at least to say what they're there for, but here I'll try to describe some of the most important ones.

  • The BuildPrettyUrls() and SetParameters() functions control are mostly used for pretty urls (more informations here)
  • The get_moduleGetVars() is used to retrieve informations from the url about other calls of the module. In most cases, this is what provides the $item->is_selected attribute of items.
  • The plResize() function (located in function.plresize.php) is used to resize images and create thumbnails
  • For each level, you will have a get_level_NAMEOFLEVEL(). There are among the most important, as they are call everything an element (or list of elements) is requested. It will be explained below.
  • The addadminlinks() function is used for the admin panel and is called from within a get_level_NAMEOFLEVEL() function. It takes every item and add links to edit, move, delete the item and so on.

The get_level functions

Each get_level_NAMEOFLEVEL() function queries elements from the database, creates an object for each, and return an array of these objects (basically, the $itemlist you're using in templates).
The function accepts several parameters, which I'll exlain here:

  • $where is an array of conditions, in the form $field=>$value. For example, $where=array("active"=>1) would mean that only the items were the "active" field is equal to 1 are queried.
  • $admin controls whether the addadminlinks() just discussed should be called. If it is, the $id and $returnid parameters are necessary. Otherwise they aren't.
  • $order controls the way the list of elements should be ordered. Possible values are "modified", "created", "name" or false (in which case the item order defined in the admin panel is used)
  • $limit controls the maximum amount of items that should be returned
  • $customwhere and $customvalues are used to override the $where parameter. When you want to create a query more complicated than the $where parameter allows, you can provide the string query in $customwhere and the array of values in $customvalues.

Hope this gets you going. If you have any question, or wish to share the modifications you've done, feel to contact me, either at the forge or on the forum.

Back to top
Can I put smarty tags in the item's fields?

Yes.

Let's say you have products which have a description field, and in that description you would like to put some smarty code. If you try and put, say, {title} in an attempt to display the page title, you should notice that "{title}" will be displayed exactly like that in the product's description. That is because items' fields aren't evaluated.

However, in your template, you may choose to evaluate the fields. For example, in your template, if instead of the simple {$item->description} you write:

{eval var=$item->description}

You will then notice that {title} is evaluated, and the page's title is displayed.

Back to top
How can I retrieve images in the display templates?

You put {$item->imagefield} in your template, and the image doesn't show up...

That's pretty much normal. $item->imagefield is itself an object, that has several attributes:

  • $item->imagefield->filepath (relative url)
  • $item->imagefield->ext (the file extension)
  • $item->imagefield->url (absolute url)
  • $item->imagefield->size (size in bytes)
  • $item->imagefield->size_wformat (formated size)
  • $item->imagefield->imagesize (for images only)
  • $item->imagefield->width (for images only)
  • $item->imagefield->height (for images only)
  • $item->imagefield->image (image tag; for images only)
  • $item->imagefield->thumbnail (thumbnail url, if applicable)
  • $item->imagefield->filemtime (last modified, unix time)
  • $item->imagefield->modified (last modified, formated time)

To display the image, you can use {$item->imagefield->image}, or if you wish more options, you can create the image tag yourself:

<img src="uploads/{$item->imagefield->filepath}" alt=""/>

Back to top
How can I display values inside an Array?

If you have a list field that allows multiple choices (checkboxes or select list) and simply use {$item->fieldname} or {$item->fieldname_namevalue} in your template, you'll probably end up with this being displayed:

Array

This is normal. As the field contains (or can contain) multiple values, it is indeed an array of values. To get the values themselves, you have to iterate through the values in a way like this:

<ul>
{foreach from=$item->fieldname_namevalue item="onevalue"}
<li>{$onevalue}</li>
{/foreach}
</ul>
Back to top
How can I change the dates display format?

Posted By: Solutic on Date: 2009-05-07 10:31
Use: {$item->yourVar|date_format:"%H:%M"}

See the smarty entry about date format modifier or the most useful smarty list of variable modifiers.

Back to top
How can I format the page menu?

The function that generates the pagemenu is split_into_pages (in module file), but you should be able to format it the way you like without modifying the function.

Classes and CSS
The pagemenu is in a div with class "pagination". Each page number is an anchor(<a>) with class "pagenumber", and the current page also has the class "current". The "..." shown when there are too many pages are spans with the class "pagemenuoverflow", and the left and right arrows are anchors respectively with classes "previouslink" and "nextlink".
You may use these classes to style your page menu. You can find examples of styles here (I tried to use the same classes, but for uniformity reasons I didn't use spans, so double-check the css).

Strings
To change the page delimiter ("|") or the "...", look at the first lines language files, respectively at the entries "pagemenudelimiter" and "pagemenuoverflow".
As for the < and >, they are hard-coded in the function split_into_pages, but it shouldn't be too difficult to change.

Back to top
How can I display a list of elements sorted in a certain way?

We will look at how to create a list of sorted elements in the following way:

  • [Sorting field]
    • [element]
    • [element]
    • [element]
  • [Sorting field]
    • [element]
    • ...

The basic idea will be to order elements by the sorting field, and notice when there's a change.


Order by parents
Create the following template (which I'll call "order_by_parents"):

			<ul>
{assign var="parentflag" value=false}
{foreach from=$itemlist item="item"}
{if $item->parent_name != $parentflag}
{if $parentflag}</ul></li>{/if}
<li>{$item->parent_name}<ul>
{assign var="parentflag" value=$item->parent_name}
{/if}
<li>{$item->name}</li>
{/foreach}
</ul>

and call it using {cms_module module="mymodule" what="item" listtemplate="order_by_parents" orderby="parent"}

Order by field
Let's say your level has a dropdown field named "myfield", and you wish to list elements sorted on that field. Create the following template (which I'll call "order_by_myfield"):

			<ul>
{assign var="fieldflag" value=false}
{foreach from=$itemlist item="item"}
{if $item->myfield != $fieldflag}
{if $fieldflag}</ul></li>{/if}
<li>{$item->myfield}<ul>
{assign var="fieldflag" value=$item->myfield}
{/if}
<li>{$item->name}</li>
{/foreach}
</ul>

and call it using {cms_module module="mymodule" what="item" listtemplate="order_by_myfield" orderby="myfield"}

Back to top
I'm trying to display a list of items, but the detail template is used instead. What's going on?

If you're on the final level and there's only one item to show, the module won't display the list view but will immediately show the details of this item, so that the user doesn't have to click twice for no reason.

If you want to display the list view anyway, there is now a "forcelist" parameter that will do the job (see the help of your module). Your tag should become:

{cms_module module="products" what="product" forcelist="1"}

To set forcelist to true by default, look here.

Back to top
Can I integrate the Comments module?

Yes. In fact, the Comments module makes this very easy for any other module.

Open the detail template for your last level elements, and simply add this:

{cms_module module="comments" modulename="NAMEOFYOURMODULE" pageid=$item->id}

Where NAMEOFYOURMODULE is, of course, the name of your module.

Back to top
Warning: module "is not properly cleaning input params." What's going on?

[*** Thanks to ikulis, this was definitely fixed in 1.8.4.2 and shouldn't be a problem anymore ***]

The advanced search form would need to register a param for each field of each level, which I find rather inelegant. For that reason the RestrictUnknownParams option has been disabled.

If you are not using the advanced search form, you can safely activate the RestrictUnknownParams() option. Edit the module file (modulename.module.php), find the SetParameters() function, and edit the following line :

		$this->RestrictUnknownParams(false);
to:
		$this->RestrictUnknownParams();

If you do use the advanced search, I would suggest disabling the "Allow parameter checks to create warning messages" in the CMS Global Settings.

Back to top
How can I retrieve informations about an object other than the one I'm watching?

In any module template, you can retrieve a whole item object using the following smarty tag: {MODULENAME_get_levelitem}.

The tag requires 3 parameters : what (retrieve an item from which level), alias (the item's alias) and assign (in which smarty variable this should be stored).
So a typical example would be:

		{mymodule_get_levelitem what="category" alias="myfirstcategory" assign="catobject"}

And you can then use the assigned object to display information:

		{$catobject->name}
Back to top
Can I change the field inputs that are displayed in the module's advanced search form?

Yes.

From CTLModuleMaker 1.8.3, it is possible to define which fields will be shown in the advanced search form upon field creation. If you have created your module with an earlier version or do not want to create it again, there's another way around.

The search form for the module's search action are generated from templates that can be found in the "templates" folder of your module.

The "search.tpl" file is the template for the search form when the "simple" searchmode is chosen.

The "search_generalresults.tpl" file is the template for the search results when we are searching in more than one level at the same time. Otherwise, the level's default list module is used.

For each level of your module, there should be a file named "search_NAMEOFTHELEVEL.tpl". This contains the template for the advanced search form for this level. If you want to remove a field input from the search form, you can simply delete the appropriate section of the template. (I would recommand to make a backup of the original template, if you ever change your mind!)

Back to top
Can I change the edit form for a level?

Yes.

The edit forms for each level are generated from templates that can be found in the "templates" folder of your module. For each level of your module, the template for the edit formre should be in a file named "editPREFIXOFTHELEVEL.tpl".

You can modify the template, but keep two things in mind:
- If you remove an input, the form might not work anymore.
- If you wish to change the field labels, you should preferably do so in the language file.

Back to top
Can I add a condition for and element's input to be accepted?

One of your level has, for example, a numeric field (which we'll call "myfield"), and you wish to make sure it's between, say, 0 and 100? Of course it's possible, but you'll need to edit the code.

Open the edit file associated with the level (action.editLEVELPREFIX.php). Somewhere not so far from the beginning of the file, you will find the following code :

		// CHECK IF THE NEEDED VALUES ARE THERE
#1 if( !isset($params["itemname"]) || $params["itemname"] == "" ){
#2 echo $this->ShowErrors($this->Lang("error_missginvalue"));
#3 }elseif(false == $this->checkalias("module_modulename_item", $item->alias, isset($params["itemid"])?$params["itemid"]:false)){
#4 echo $this->ShowErrors($this->Lang("error_alreadyexists"));
#5 }else{
############ DOING THE UPDATE

Here two conditions are checked before updating/adding the element: first, on line #1, we check if a name was given (and any other mandatory value, if any), and if it wasn't, we display an error (line #2). On line #3, we check if another element already has the same alias and display an error message (line #4) if it is the case. If no problem is met, we get to line #5 and go on with the update.

Basically, you can add any condition you like to these. So with our example, it could look like this:

		// CHECK IF THE NEEDED VALUES ARE THERE
if( !isset($params["itemname"]) || $params["itemname"] == "" ){
echo $this->ShowErrors($this->Lang("error_missginvalue"));
}elseif(false == $this->checkalias("module_modulename_item", $item->alias, isset($params["itemid"])?$params["itemid"]:false)){
echo $this->ShowErrors($this->Lang("error_alreadyexists"));
}elseif( false == ($item->myfield > 0 && $item->myfield <= 100) ){
echo $this->ShowErrors($this->Lang("my_custom_error_message"));
}else{
############ DOING THE UPDATE

We just check if the field is over 0 and below/equal to 100. If it is, we go on with the submission. Otherwise we display an error message (which you'd need to add to the language files, otherwise you can hardcode the error message...)

Back to top
How are the defaultadmin panels generated?

If you open the /templates/ folder, you will notice that there's only one template for the defaultadmin : adminpanel.tpl. This is used for all levels. So the difference is the information passed to the template. Before taking a look at it, let's see that information.
Open the action.defaultadmin.php of your module. You will notice that it is divided into tabs (delimited by echo $this->StartTab("nameoflevel"); and echo $this->EndTab();). For each level tab, the list of its items is fetched and stored into $itemlist, which is then passed to smarty. $itemlist is an array of objects having your fields as attributes ($item->field).
Just below that, you will notice lines that look like this:

		$adminshow = array(
array($this->Lang("name"),"editlink",false),
array($this->Lang("alias"),"alias",false),
array($this->Lang("active"),"toggleactive",true),
array($this->Lang("nbchildren"),"nbchildren",false),
array($this->Lang("reorder"),"movelinks",true),
array($this->Lang("Actions"),"deletelink",true)
);
$this->smarty->assign("adminshow", $adminshow);
$this->smarty->assign("tableid", "levelname_table");
echo $this->ProcessTemplate("adminpanel.tpl");

The $adminshow variable contains what should be shown in the adminpanel of that level. We then pass this variable to smarty (and also a name for the table), and the last line tells smarty to display the template "adminpanel.tpl".
$adminshow is an array, where each element represents a column of the adminpanel. Each column, in turn, is an array containing three values: the first (position 0) is the title of the column, and the second (position 1) is the name of the object's field that should be shown in this column, and the third (position 2) is whether the instant search (the searchbox just at the top of the adminpanel) should skip this column.

For example, array($this->Lang("name"),"editlink",false) means, here, that we take $this->Lang("name") ("name" in the language that is currently displayed) to be the title of the column, and that for each row of the table, whatever is in $oneitem->editlink will be displayed (this particular value is not strictly speaking a field of the level, but an attribute created by the addadminlinks() function). It also means that the instant search will not skip this column, so the text displayed will be used to see if the row matches the search words.

Now, let's take a look at the adminpanel.tpl file:

#1		<div>
#2 <table {if $tableid}id="{$tableid}" {/if}cellspacing="0" class="pagetable">
#3 <thead><tr>
#4 {foreach from=$adminshow item=column}
#5 <th>{$column[0]}</th>
#6 {/foreach}
#7 </tr></thead>
#8 <tbody>
#9 {cycle values="row2,row1" assign=rowclass reset=true}
#10 {foreach from=$itemlist item=oneitem}
#11 {cycle values="row2,row1" assign=rowclass}
#12 <tr class="{$rowclass}" onmouseover="this.className='{$rowclass}hover';" onmouseout="this.className='{$rowclass}';">
#13 {foreach from=$adminshow item=column}
#14 {assign var=oneval value=$column[1]}
#15 <td{if $column[2]} class="ctlmm_nosearch"{/if}>{$oneitem->$oneval}</td>
#16 {/foreach}
#17 </tr>
#18 {/foreach}
#19 </tbody>
#20 </table>
#21 </div>

Now, I will assume that everyone understands the html tags, and will leave alone the code concerning the classes. I will explain lines #4 to #6, which write the table header (the titles of the columns), and lines #9 to #18, which write the table rows.

Line #5 is executed for each element of the $adminshow variable; in other words, for each column of the table. Now, remember that each element of the $adminshow variable is an array of two elements, so what line #5 does is display the first (position [0]) of these two elements as the column header.

Lines #11 to #17 are repeated for each item in $itemlist (for each row of the table). For each row, for each element of the $adminshow variable, lines #14 and #15 are repeated. Line #14 fetch the name of the object's attribute that should be displayed (position [1] of the adminshow element), and line #15 displays this attribute of the current object.

As of the ctlmm_nosearch class, it only means that the instant search will not check into this column.

Alright... how can I change this?

If you want to make major changes, the best thing to do would be to create an individual template for your level. In the templates/ folder, create a file (like "adminpanel_nameoflevel.tpl"), put the template you wish to use in it, and in the according tab of the action.defaultadmin.php file, change
echo $this->ProcessTemplate("adminpanel.tpl");
to
echo $this->ProcessTemplate("adminpanel_nameoflevel.tpl");

Now, let's say you wish to make a very simple modification. For example, you are displaying a file field (which we'll call "myfield") on the adminpanel, but instead of showing the filepath you would like to show the picture. What you could do is this:
In the adminpanel.tpl template file, you could change line #15 to something like this:

			<td{if $column[2]} class="ctlmm_nosearch"{/if}>
{if $oneval == 'myfield'}
{if $oneitem->$oneval != ""}<img src="{root_url}/uploads/{$oneitem->$oneval}" />{/if}
{else}
{$oneitem->$oneval}
{/if}</td>

This means that smarty checks if the field we're displaying is 'myfield', if it is, and if the value isn't empty, it displays the image. If it isn't, it just displays the field value normally.

Back to top
Could I show the name of the selected item in, say, the page's <title> tag?

[*** See update below... ***]

The short answer: no.

The long answer: yes, most of the time.

The name of the selected item or category is retrieved only when the {cms_module module="yourmodule"} tag is encountered in the template - which is quite always way after the page's head. However, in most cases in could be retrieved by a user-defined tag (UDT).

Note that this will no work when the tag is directly called on the page, and will only work when we are following a module action (when you have clicked on a link)

Create the following UDT, replacing NAME_OF_YOUR_MODULE with your module's name:

$modulename = 'NAME_OF_YOUR_MODULE';
global $gCms;
if( isset($gCms->modules[$modulename]) &&
$gCms->modules[$modulename]['active'] &&
isset($params['assign']) ){
global $smarty;
$instance = $gCms->modules[$modulename]['object'];
$glob = $instance->get_moduleGetVars();
$wantedlevel = false;
$modlevels = $instance->get_levelarray();
if(isset($glob['alias'])){
$wantedlevel = isset($glob['what'])?$glob['what']:$modlevels[count($modlevels)-1];
$alias = $glob['alias'];
}elseif(isset($glob['parent'])){
if(isset($glob['what'])){
$wantedlevel = $instance->get_nextlevel($glob['what'], false);
}else{
$wantedlevel =$modlevels[count($modlevels)-2];
}
$alias = $glob['parent'];
}
if($wantedlevel){
$getfunction = 'get_level_'.$wantedlevel;
$item = $instance->$getfunction(array('alias'=>$alias));
if(isset($item[0])) $smarty->assign($params['assign'],$item[0]);
}
}

Let's say you've named your tag "retrievemodinfo". Then, at the beginning of your page's template, you retrieve the module informations by using the tag {retrievemodinfo assign="currentelement"}. This means that the information about the current item are stored in the variable $currentelement (which could have been anything). So in the <title> tag of your template, you could write: {if $currentelement}{$currentelement->name}{/if}, which would display the name of the current element if it could have been retrieved.

UPDATE: since version 1.8, you can do the same thing much more easily just by using the "breadcrumbs" action and its parameters.

Back to top
How can I add module elements to my google sitemap?

This will help you include the module entries to a google sitemap, provided that your module elements are all displayed on the same page.


If you are using the google sitemap generator module:

Open your sitemap generator (gsitemap.php), and just before the closure of the urlset (echo ' </urlset>'. "\n";), add the following lines:

	$modulename = "NAMEOFMODULE";	// CHANGE THIS TO THE NAME OF YOUR MODULE
if( isset($gCms->modules[$modulename])
&& $gCms->modules[$modulename]["active"]
&& $themodule = $gCms->modules[$modulename]["object"]
){

// HERE YOU MUST PUT THE ID OR ALIAS OF THE PAGE USED TO DISPLAY THE MODULE ELEMENTS:
$detailpage = "MYPAGE";

$params = array("mode"=>"google", "detailpage"=>$detailpage);
$themodule->DoAction("sitemap", "", $params);
}

Of course, replace NAMEOFMODULE in the first line with the name of your module, and enter a detailpage, be it an id or page alias.

Note that you could also use the "what" parameter to specify which levels you wish to see displayed (you can specify several levels with "level1|level2" and so on). Otherwise, all levels will be displayed.


If you are using the SiteMap Made Simple module:

Simply put the code above in a UDT (user-defined-tag), and call the UDT at the end of your sitemap's template, just before </urlset>.

Back to top
How can I add an expiration field?

You wish to add a date field that will decide when the item will be displayed or not (like in the news module). It shouldn't be very hard.

To do this, you will need two fields (or three):

  • A "checkbox" type field to choose whether the expiration date will be used or not (we'll call it "use_expiration")
  • A "date" type field to choose the expiration date (we'll call it "expiration_date")
  • (optionally) Another date field (for begin date)

For simplicity, I will assume we need only one date (you should figure out the part for the second date).

In your list template, use the following code:

	{foreach from=$itemlist item="item"}
{if $item->use_expiration && ($item->expiration_date|date_format:"%s" < $smarty.now)}
the code to display the item...
{/if}
{/foreach}

Basically, we only display the item when a condition is met, namely that use_expiration is on, and that the expiration date is lower (before) the current date. To do the opposite, you can just change the < to a >.

Back to top
How can I setup the frontend edit action with the Front End Users module?

This is done in two parts. The first part is to display the edit link or edit form only to selected users (or logged in users). This is to be done with the FEU module (and possibly Custom Content), and as such will not be covered here. This is only about hiding the edition from other users. The second part, which will be covered here, is about the real protection. Sadly, this will require a little programming, but everyone should be able to get through this easily.

If you're wondering how to change the look of the form, see the frontend_add_*.tpl templates in the templates folder.

Before editing an item through the frontend, the function feadd_permcheck (whose content is in the file function.feadd_permcheck.php) is called. If it returns true (if the value of the $return variable is set to true), the user has permission to edit the item, otherwise (if it returns false), the access is denied.
If you open the file function.feadd_permcheck.php, you will notice that aside from comments, the function is empty. We will have to fill it out depending on what we wish to do.

The variables at our disposition are the following:

  • $gCms (the global cms object, through which we will be able to call the FEU module)
  • $what (the level of the item we are attempting to edit, as given by the "what" parameter)
  • $alias (the alias of the item we are attempting to edit. If it is a new item, $alias should be false)
  • $itemid (the id of the item we are attempting to edit. If it is a new item, $itemid should be false)

You might need the following functions of the FEU module:

  • LoggedIn() will return true if the user is logged in, and false otherwise.
  • LoggedInId() will return the id of the user
  • LoggedInName() will return the username of the user
  • MemberOfGroup($userid,$groupid) returns true if the user is a member of the group, false otherwise
  • GetGroupID($groupname) returns the group id associated to a group name


Now, here are four basic examples of how to use this.

Allowing only logged in users
Here's how to allow only logged in users to edit items:

Failsafe:

place the below code in a page with  WYSIWYG turned off:

{assign var=userids value=$gCms->modules.FrontEndUsers.object->LoggedinId()}
{assign var=username value=$gCms->modules.FrontEndUsers.object->GetUserName($userids)}

{cms_module module=FrontEndUsers}

{if $userids!=''}

{cms_module module="youtubeplayer" action="frontend_edit" what="videos"}

{/if}

Or:

	$return = false;
$FEU = $this->GetModuleInstance('FrontEndUsers');
if($FEU && $FEU->LoggedIn()){
$return = true;
}

Allowing only members of a group
Here's how to allow only members of the group "mygroup" to edit items:

	$return = false;
$FEU = $this->GetModuleInstance('FrontEndUsers');
if($FEU){
if($FEU->LoggedIn()){
$userid = $FEU->LoggedInId();
$groupid = $FEU->GetGroupID("mygroup");
if($FEU->MemberOfGroup($userid, $groupid)){
$return = true;
}
}
}

Allowing members to edit only a single element
Suppose you have a FEU member corresponding to each of your item, and that the username of that member is the alias of the item (see below on how to do this). You could restrict access in the following way:

	$return = false;
$FEU = $this->GetModuleInstance('FrontEndUsers');
if($FEU){
if($FEU->LoggedIn()){
$username = $FEU->LoggedInName();
if($username == $alias){
$return = true;
}
}
}

Using $FEU->GetUserProperty($propertyname), you could use something else than the username.
If permissions depend on the alias of the item, you should make sure (see Settings) that your frontend users can't change it.


Should you need more informations about the item, you can use the following snippet to load the full item object:

	$getfunction = "get_level_".$what;
$itemlist = $this->$getfunction(array("id"=>$itemid));
$item = isset($itemlist[0])?$itemlist[0]:false;

Finally, you may use $what variable to have different permission management for each level. A switch would be the typical solution:

	switch($what){
case "level1":
// code
break;
case "level2":
//code
break;
...(and so on)
}

Triggering the creation of a FEU member for each item
If you're using something similar to the last example, you need to have a FEU user for each item. This can be done using events.
Everytime an item is added, modified or deleted, the modules sends an event to the cms. If you go into Extensions -> Event Manager, you should find the events sent by your module in the list. Click, for example, on the modulename_added event. You will notice that here, you can select a User Defined Tag (UDT) that will be called when the event is triggered. So we simply need to design an UDT that will add a member when a new item is created.

We will assume your item has a field named "password", which will be used as the FEU password, that your module is named "NAMEOFMODULE", and the level is named "MYLEVEL". Look at the following UDT:

	// first, we check if an item has been added on the level we're concerned with
if($params["what"] != "MYLEVEL") return false;

// we retrieve the two modules
$FEU = $this->GetModuleInstance('FrontEndUsers');
$mymod = $this->GetModuleInstance('NAMEOFMODULE');
if(!$FEU || !$mymod) return false;

// we then retrieve the item
$itemlist = $mymod->get_MYLEVEL(array("id"=>$params["itemid"]));
if(!isset($itemlist[0])) return false;
$item = $itemlist[0];

// we then add the FEU user
$expires_in = 365; // sets the number of days until expiration
$expires = strtotime(sprintf("+%d days",$expires_in), time());
$FEU->AddUser($item->name, $item->password, $expires);
Back to top
Can I change how the pretty urls are generated and interpreted?

Yes, to some extent.

I'll try to make this very comprehensive. Before talking of modying it, let's see how this works.

Basically, the module has two kinds of urls:
Type A: http://www.mydomain.com/nameofmodule/detail/nameofobject/5/
and
Type B: http://www.mydomain.com/nameofmodule/nameoflevel/nameofparent/5/
(if you are separating elements into pages, you will notice that type B has some variations... we will discuss this later.)

Type A is used when we are looking at the details of a last-level object. Type B is used when we display a list of items. Now, remember that the url has to contain all the information about what should be displayed. So let's take a look about what's common to both types of urls:

The nameofmodule/ tells us that the module should be called.
The 5/ (that annoying number) is the returnid of the page. In other words, it tells us on what page of the cms the informations of the module should be shown. Even though the page content is replaced by what the module has to display, this is very important as it determines, for instance, what page template should be used.

When the /detail/ is in the url, the module knows that we're displaying the details of a last level item. All we need to specify is which item to display, and thats the nameofobject part.

When the /detail/ isn't in the url, the module concludes that we are displaying a list of something*. The elements of what level are displayed is determined by whatever is in the place of "/detail/": in our case, nameoflevel/. A url like http://www.mydomain.com/nameofmodule/product/5/ would be valid, and would tell the module to display all elements of the "product" level.
The /nameofparent/ part tells the module to display only the items of the chosen level that have "nameofparent" as a parent.

The pretty urls are registered in the SetParameters() function of the main module file (which should be something like NAMEOFYOURMODULE.module.php). Open this file, and do a search to find the function. What matters in there is the lines that look like this (without the numbers on the left) :

#1	$this->RegisterRoute("/[nN]ameofmodule\/([Dd]etail)\/(?P[^\/]+)\/(?P[0-9]+)$/");
#2 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[0-9]+)$/");
#3 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[^\/]+)\/(?P[0-9]+)$/");
#4 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[0-9]+)\/(?P[0-9]+)\/(?P[0-9]+)$/");
#5 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[^\/]+)\/(?P[0-9]+)\/(?P[0-9]+)\/(?P[0-9]+)$/");

Each line here register a different url structure.
Line #1 registers the Type A urls.
All the other lines register Type B urls:
Line #2 is for when no parent is specified, like in http://www.mydomain.com/nameofmodule/nameoflevel/5
Line #3 is used when a parent is specified, like in http://www.mydomain.com/nameofmodule/nameoflevel/nameofparent/5
Line #4 is a variation of line #2 that handles separation into pages (when we separate into pages, we need to know which page we're looking at). For example, http://www.mydomain.com/nameofmodule/product/2/10/5 would mean that we are looking at the list of all items of the "product" level, that we are separating in pages with "10" elements per page, and that we are looking at the page "2".
In pretty much the same way, line #5 is a variation of line #3.

The <paramname> thing means that whatever is in that place in the url will be interpreted as the "paramname" parameter. In other words, each url structure output different parameters that will tell the module what to do.

Modifying all this...
There are some things that you can change, some that you can't, and some that you can at a price.

Let's say you don't like the word "detail" in the url, and would like to change it to something else**. First, let's not that the "[Dd]" part in the url structure says that there can be either a "D" or a "d" here. In other words, http://www.mydomain.com/nameofmodule/detail/nameofobject/5/ is the same thing as http://www.mydomain.com/nameofmodule/Detail/nameofobject/5/.
You can change the word "detail" for something else, but you have to be careful and remember that the url carry all necessary information about what should be shown. For example, if one of your level is named "product", you couldn't replace the word "detail" by the word "product". If you did, the module couldn't know if the word "product" it encounters means that we're watching the details of an item, or if it means that we are watching a list of the items in the "product" level.

So let's say we want to change "detail" for "view" (provided that you don't have a level called "view"). In line #1, we would replace ([Dd]etail) with (view) (or ([Vv]iew), if you want it to accept both "View" and "view").

But this is not enough. You've changed the way the urls are interpreted, but not the way they are created. In the same file, search for the BuildPrettyURLs function. It should contain something like this:

#1		$prettyurl = "nameofmodule/";
#2 if(isset($params["alias"])){
#3 $prettyurl .= "detail/".$params["alias"];
#4 }elseif(isset($params["parent"])){
#5 $prettyurl .= $params["what"]."/".$params["parent"];
#6 }else{
#7 $prettyurl .= $params["what"];
#8 }
#9 if(!isset($params["alias"]) && isset($params["pageindex"]) && isset($params["nbperpage"])) $prettyurl .= "/".$params["pageindex"]."/".$params["nbperpage"];
#10 $prettyurl .= "/".$returnid;
#11 return $prettyurl;

This function transforms the parameters into a url.
Line #1 starts with the module's name.
In lines #2-#3, if we want to watch the details of an item, we add the "detail/" block to the url.
If this isn't the case, we add the level of the items we want to show and, if specified, the parent (lines #4 to #8). In line #9, if we're separating into pages we add the page we wish to look and the number of elements per page. Finally, in line #10, we add the id of the page in which it should be displayed.

If we changed "[Dd]etail" to "[Vv]iew" in the SetParameters() function, we must also change the "detail/" in line #3 for "view/".
Now, this should do the trick.

Can I get rid of the returnid?
No, not really. However, if you the module is always displayed in the same page (i.e., if it's always the same number), we could remove it from the url and provide it in another way. Here's how to do it:

Let's say the id of the page in which the module is always called (the number we're trying to get rid of) is 5.
1. in the SetParameters() function of your module, remove all \/(?P[0-9]+) parts. (it would be a good idea to copy and paste the original lines just in case... you can comment lines using //).
2. in the BuildPrettyURLs() function of your module, comment (add // before it) the line #10 (// $prettyurl .= "/".$returnid;).
3. we now need to provide the id. Go back to the SetParameters() function, and for each line, just after the $"/ part (and before the closure of the parenthesis), add , array("returnid"=>5) (with the comma), where "5" is your id. For example, this:
$this->RegisterRoute("/[nN]ameofmodule\/([Dd]etail)\/(?P[^\/]+)$/");
should become:
$this->RegisterRoute("/[nN]ameofmodule\/([Dd]etail)\/(?P[^\/]+)$/", array("returnid"=>5));
(Remember not just to copy and paste lines from this help, because your module is probably not named "nameofmodule")
This should do the trick, but the module will never use another page to display its content.

Final notice: If you change the pretty urls and make use of the "is_selected" attributes in your templates, you might also have to change the get_moduleGetVars() function (take a look here).


* if there is only one item to display, and if you aren't using the "forcelist" parameter, the details will be displayed instead of a list.
** no, you can't just get rid of it, unless you change all registered urls... for example, instead of having a "/detail/", all other urls could have a "/list/" block.

Back to top
Can I make my module always act as if the "forcelist" parameter was enabled?

Since 1.8.6, this has become a simple option to turn on in the "Settings" tab...

Yes.

Open the action.default.php file. Around the line #16 you should see something like this:

		$forcelist = isset($params["forcelist"])?$params["forcelist"]:false;

What this says is that if the forcelist parameter has been set, we use this value, and if it hasn't, we use the value "false".
Changing this false to a true would mean that the forcelist mode is always enabled, unless specified otherwise.
But that's not exactly what we want, for this would mean that we always display a list, even when we're trying to display an item's details. So the proper code would be:

		$forcelist = isset($params["forcelist"])?$params["forcelist"]:(isset($params["alias"])?false:true);

This way, $forcelist will be false unless specified otherwise, and unless an alias (used in detail links) is specified.

(Two lines below, you could do the same kind of thing for the "inline" parameter...)

Back to top
What's with this get_moduleGetVars() function?

What is it for? And why is it such a mess?

In the main module file, there is a function called get_moduleGetVars(). Basically, the function retrieve the module action parameters from the url.
Imagine that in your template, in a column on the left, you call the module to display a category list. When you click on an category, the list stays there, but the main content block displays the children within that category. Now, in the category list, you might want to show which category is selected (using the $item->is_selected attribute). But the problem is that the category list is called before the main module action (the list of children), and thus the action parameters aren't yet parsed.
This subject has been discussed in the forums (here), and I chose the solution that is the easiest to implement for the user: retrieve the parameters from the url even before they are parsed by the cms. It's a rather complicated function because we need to deal with the possibility of prettyurls and rewritten urls...
Of course, it means that if you wish to change the way prettyurls are made and still use the is_selected attribute, you're gonna have to play in this inelegant code. But hey, I'm writing this just to make it clearer...


How does it work, and how to change it?

Before we go, here's a look at the function:

	function get_moduleGetVars(){
#1 // unorthodox hack so that different calls of the module speak with each other
#2 // basically, we retrieve parameters in the url that were meant for other instances of the module
#3 global $_GET;
#4 global $gCms;
#5 $globalmodulevars = array();
#6 if(isset($_GET["mact"])){
#7 // if we aren't using pretty urls...
#8 $modinfo = explode(",",$_GET["mact"]);
#9 if(isset($modinfo) && $modinfo[0] == $this->GetName()){
#10 if(isset($_GET[$modinfo[1]."parent"]))
#11 $globalmodulevars["parent"]=$_GET[$modinfo[1]."parent"];
#12 if(isset($_GET[$modinfo[1]."what"]))
#13 $globalmodulevars["what"]=$_GET[$modinfo[1]."what"];
#14 if(isset($_GET[$modinfo[1]."alias"]))
#15 $globalmodulevars["alias"]=$_GET[$modinfo[1]."alias"];
#16 if(isset($_GET[$modinfo[1]."pageindex"]))
#17 $globalmodulevars["pageindex"]=$_GET[$modinfo[1]."pageindex"];
#18 }
#19 }elseif($gCms->config["internal_pretty_urls"] || $gCms->config["assume_mod_rewrite"]){
#20 $params = array();
#21 if($gCms->config["assume_mod_rewrite"] && isset($_GET["page"])){
#22 // if we are using an external mod_rewrite, assuming you are using the very
#23 // basic rewrite which puts the module informations inside the page variable
#24 $parts = explode("/",$_GET["page"]);
#25 foreach($parts as $part){
#26 if($part != "") $params[] = $part;
#27 }
#28 }elseif(!$gCms->config["assume_mod_rewrite"] && $gCms->config["internal_pretty_urls"] && isset($_SERVER["REQUEST_URI"])){
#29 // if we are using the internal pretty urls
#30 $parts = explode("/",$_SERVER["REQUEST_URI"]);
#31 $started = false;
#32 foreach($parts as $part){
#33 if($started && $part != "") $params[] = $part;
#34 if(strtolower($part) == "index.php") $started = true;
#35 }
#36 }
#37 if(isset($params[0]) && strtolower($params[0]) == strtolower($this->GetName())){
#38 // we are in a module action
#39 if(!isset($params[1]) || strtolower($params[1]) == "query"){
#40
#41 }elseif(isset($params[1]) && strtolower($params[1]) == "detail"){
#42 $globalmodulevars["what"] = "tsuba";
#43 $globalmodulevars["alias"] = $params[2];
#44 }else{
#45 $globalmodulevars["what"] = $params[1];
#46 switch(count($params)){
#47 case 6:
#48 $globalmodulevars["pageindex"] = $params[3];
#49 $globalmodulevars["nbperpage"] = $params[4];
#50 case 4:
#51 $globalmodulevars["parent"] = $params[2];
#52 break;
#53 case 5:
#54 $globalmodulevars["pageindex"] = $params[2];
#55 $globalmodulevars["nbperpage"] = $params[3];
#56 break;
#57 }
#58 }
#59 }
#60 }
#61 return $globalmodulevars;
}

Now there are four possible scenarios:
1. There is no rewrite or prettyurls at all (that's the easiest... lines 7 to 18 deal with this)
2. We are using internal prettyurls without mod_rewrite (lines 29 to 35, and 37 to 59)
3. We are using internal prettyurls along with the standard assumed mod_rewrite (lines 22 to 37, and 37 to 59)
4. We are using a non-standard mod_rewrite... in that case, you'll either have to forget about the is_selected attribute or to write the code yourself.

Case 1: good ol' urls
If we find the "mact" in the GET parameters, then we are dealing with standard urls. We just have to break the mact value into pieces, make sure it's an action of our module, and retrieve the different parameters by their name (along with the $id).

Case 2: internal pretty urls
Lines 29 to 35 retrieve the url, separate it into parts (each part being a fake folder in the url), and only keep the parts that are after the "index.php/".

Lines 37 to 59 try to figure just what parameters these parts could be. Remember that we had the following routes registered:

#1	$this->RegisterRoute("/[nN]ameofmodule\/([Dd]etail)\/(?P[^\/]+)\/(?P[0-9]+)$/");
#2 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[0-9]+)$/");
#3 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[^\/]+)\/(?P[0-9]+)$/");
#4 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[0-9]+)\/(?P[0-9]+)\/(?P[0-9]+)$/");
#5 $this->RegisterRoute("/[nN]ameofmodule\/(?P[^\/]+)\/(?P[^\/]+)\/(?P[0-9]+)\/(?P[0-9]+)\/(?P[0-9]+)$/");

Route #1 is handled by the lines 41-43. Otherwise, it will depend on the number of parameters. If there are 6, we know we're using route #5 : $params[0] is the name of the module, $params[1] the name of the level (what), $params[2] the name of the parent, $params[3] the pageindex, and so on...
Likewise, if we have 5 params, we know we're using route #4, and with 4 params, we know we're using route #3. If we were using route #2, there wouldn't be anything to retrieve anyway.
If the module action is a query (lines 39-40), we don't retrieve anything...

So basically, if you change the routes, you've got to change lines 37 to 59.

Case 3: internal pretty urls with standard mod_rewrite
In this case, the path that's after "index.php/" in case 2 is simply passed through the "page" GET parameter. So once we've retrieved it, the task is just like case 2.

Back to top

Pierre-Luc Germain