Monthly Archives: August 2013

Open Data Records in new Intrexx Popups

This post is in response to a question asked on the Intrexx Live! forum.

In Intrexx you can configure links to open the target page in a new window. This is called a Popup and is opposed to Tooltips which are opened within the current window, either displayed as a contex help or as a modal overlay on top of the page.

Open as Popup

To open the target page as a popup go to the Actions tab within the button properties. Under Destination chose Open as popup.

Configure a link as popup

Individual Popups from View Tables

A common use case is to have a list of data records shown in a view table. Then, by clicking on the name or description of the entries or via a dedicated button the data records open in separate popup windows. This allows the user to move and arrange the windows next to each other so that they can compare the data records.
By default, Intrexx will create a single popup window and update it when the user clicks on another entry. To have links in a view table open as individual popups open the properties window. In the Expert tab add a new attribute called sameWindow with the value no.

Individual Popups from Shaped Tables

When you want to have an especially stylized interface you usually opt for a Free Layout Table, also called Shaped Table, instead of a standard View Table. The shaped table uses a view page to display the entries. If you want to open individual popups from the shaped table’s view page, however, the above technique will fail. You can add a button to the shaped table’s view page and configure it to open as popup. But the sameWindow expert attribute has no effect here.

If you still want to have your individual popups you can just build the link manually. Place a static text field, set to Programming, on the shaped table’s view page. Using HTML and Velocity you can write the code to create the link in here. To do this you need to know a few things:

  • The neccessary HTML markup is described in the Links in Intrexx tutorial. Basically you need an A element with the data-hijax="popup" attribute and a href attribute that targets the Application (rq_AppGuid), the page to open (rq_TargetPageGuid) and the data record’s id (rq_RecId).
  • To access the current data record’s id when inside of a view table or a shaped table use the $drRecord Velocity context object: $drRecord.getRecId().
  • The url in the A element expects hex-encoded record IDs. You can use the $Codec Velocity context object for this: $Codec.hexEncodeString("Hello", "UTF-8")

The resulting code for the programmable text field looks like this:

<a href="?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_RecId=$Codec.hexEncodeString($drRecord.getRecId(), "UTF-8")"
  data-hijax="popup"
  class="Button_Standard">Click me</a>

Replace the GUIDs for rq_AppGuid and rq_TargetPageGuid with yours.

Referencing data fields in the page

Chances are that you do not want to have a static text like “open” as the link. You could replace the Click me in the snippet above with a nice image. Usually you will want to display the value of a data field such as a title or name and wrap that in the link. To do so you need to reference the field’s value for the current data record.

You can make individual data fields from the data group excplicitly available in view/edit pages. First, get the GUID of the data field that you want to use on your page by going to the data group and looking up the properties of the data field.
Now open the view page that is used in the shaped table and go to the Expert tab. Switch to the Settings pane and add a new setting. Up to Intrexx 5.2 you had to enter two separate keys:
page.requiredDataField.?.guid takes the GUID of the data field you want to access.
page.requiredDataField.?.identifier defines the name by which you want to access the data field. This can be any name, such as myVar, name, title, you get the idea.
By assigning a key index you map the guid to the identifier. If you want to reference multiple data fields use different keys (1, 2, 3, …) for each pair of guid and identifier.

As of Intrexx 6 this is even better supported: Simply enter page.requiredDataField.?. The dialog now contains a table where you can fill in the GUID and name.

Once you told Intrexx to make the data field available in the page you can acces it in Velocity with $drRecord.getValueHolder('<name>').getValue(). Note that you have to use $drRecord because the page is used inside a shaped table. If you want to access a data field in a normal edit or view page, use $DC.getValueHolder('<name>').getValue() instead.

The resulting code for a link that opens individual popups and displays the name of the data records looks like this:

<a href="?rq_AppGuid=D8D...E6E&rq_TargetPageGuid=ED1...EFD&rq_RecId=$Codec.hexEncodeString($drRecord.getRecId(), "UTF-8")"
  data-hijax="popup"
  class="Button_Standard">$drRecord.getValueHolder('name').getValue()</a>

Improving Intrexx Session Security

In Intrexx a user’s session is identified by the session ID. This ID is usually hidden from the user, i.e. it is stored in a browser cookie (co_SId). When the session ID is not saved as a cookie it is passed as a request parameter: default.ixsp?rq_SId=521...F0F. This is when things get dangerous: If another user gets hold of your session ID, they can log in with your account – without needing to know your username or password. If your Intrexx portal features applications with various user rights and confidential information you should take steps to prevent this from happening.

Session Hijacking

You may be thinking: Why would a user want to steal another colleague’s session ID? We are mostly speaking of intranets here. And how would they know how to do it?
The point is, when cookies are disabled in your browser, Intrexx has no choice other than to store the session ID via the URL. A few years ago, considering cookies a security risk was all the rage. Things are different today and with Web Storage there is another option with good browser support. But the way things are, the session ID gets exposed as easily as disabling cookies in your browser. To make things worse, there are still a few situations where Intrexx will put the session ID in the URL even with cookies enabled. These situations are rare and will most likely die off over the coming updates and as new versions of the software are released. Still, as of today session IDs in URLs are a viable security risk.

Session Fixation

Once you have another user’s session ID session fixation is very easy to do. All you need to do is insert the parameter to the portal’s URL: rq_SId=521...F0F. The problem is that users will share their session ID unconsciously. They have no idea what the various parameters such as rq_AppGuid, rq_RecId or rq_MenuGuid stand for and they shouldn’t need to. Often times they will want to share a specific page of the intranet with a colleague so naturally they will copy and send their URL address, just like they are used to from most websites.

Session Security Concepts

To more reliably bind a session to a specific user you need to store additional information about the client as part of their session. Then, whenever a page is opened check if these data still match the requesting client. Two common parameters for PHP session security are the following. I will use these ideas as the basis for an Intrexx implementation. To learn more about this topic, visit the PHP Security Consortium or read a book.

Check for User Agent

The user agent describes the user’s browser type. Chances are that a different user will also have a different user agent. However, this is mostly useful for public internet websites. In a closely controlled corporate environment most colleagues will be using the same browser. Another aspect to be aware of is that the user agent can be easily modified on the client-side, decreasing its reliability. Still, it is an indicator that something might be fishy. Also you might explicitly want to allow users to browse your site with two different browsers (though typically you won’t plus the average user wouldn’t know how to “reuse” their own session anyway).

Check for IP address

Most of the time different users will have different IP addresses and it is highly unlikely that they will change during a session. Again, a corporate environment can be problematic as users surfing behind a proxy will probably expose the proxy’s IP address. The PHP Security Consortium has this to say about it:

It is unwise to rely on anything at the TCP/IP level, such as IP address, because these are lower level protocols that are not intended to accommodate activities taking place at the HTTP level. A single user can potentially have a different IP address for each request, and multiple users can potentially have the same IP address.
PHP Security Consortium

So see for yourself whether this applies to your target group and environment’s set-up. Verifying the IP address can be a viable and suitable solution in some cases.

Implementing Session Security in Intrexx

The above concepts can be easily implemented in Intrexx. The client’s user agent can be retrieved using Velocity with $Browser.getUserAgentId() (or $Request.get("HTTP_USER_AGENT")), the IP address is available via $Request.REMOTE_ADDR. The full code for a Velocity macro that sets the user’s fingerprint based on these two attributes and verifies it against the current session could look like this:

#*
        Creates a fingerprint for the client based on the IP address and the user agent
        and stores it as part of the session.
        On every page where this macro is called the session will be verified against the
        actual current client data.
*#
#macro(verifySession)

        #set($strUserAgent = $Browser.getUserAgentId())
        #set($strIPAddress = "$Request.REMOTE_ADDR")
        #set($strFingerprint = "${strUserAgent}${strIPAddress}")
        #set($strFingerprint = $TextUtil.stringToHex($strFingerprint))
       
        ## Initialize Session:
        #if(!$Session.containsKey('USER_FINGERPRINT'))
                $DEBUG.info("Init session fingerprint")
                 $Session.put('USER_FINGERPRINT',$strFingerprint);
        ## If Session has been initialized...
        #else
                 #set($strOriginalFingerprint = $Session.get('USER_FINGERPRINT'))

                 ## Session does not fit to user:
                 #if($strOriginalFingerprint != $strFingerprint)
                        $DEBUG.error("[custom_preload.vm] Possible session fixation detected! Logging out...")
                        $Session.logout()
                 #end
        #end

#end

If a mismatch between the client’s data and the session data is detected $Session.logout() will immediately log out the current user and redirect them to the start page. Note, that if you have Single-Sign-On enabled, the user may not be redirected, but instead the page may open but with no working links (since the user is logged out). In that case you might want to trigger a client-side redirect, e.g. with makeAppRequest().

Normaly you would use some cryptographic hash function to obscure the stored IP address and user agent information just like you would do with passwords. For the sake of simplicity I am using $TextUtil.stringToHex($strFingerprint) instead here, which wouldn’t be a sensible solution for production code since it is reversible via $TextUtil.hexToString(). However, creating a proper getHash() function is not all that hard and nicely described in the wiki of the Open Web Application Security Project (OWASP). All you need to do is create a Java class with the given code, export as JAR-file and include it in Intrexx as a customcallable (read here). If you are running a larger portal with lots of functionality chances are that you already have some utility class that is just waiting for another useful method.

Now where to put this code? Simply copy the code into the custom_preload.vm file at internal/system/vm/custom/custom_preload.vm. This file is automatically included into each Intrexx page. Now you can call the macro #verifySession() on those pages that you want to protect using the programmable static text field or simply call it for all pages.

Generating Table of Contents with JavaScript

Inspired by Chris Coyier’s article at CSS-Tricks I decided to implement dynamically generated tables of contents for this blog.

Goals and Features

The purpose of the JavaScript function is to generate a table of contents for articles based on the headings found inside. So far, so good. Now, if you take a deeper look at it, there are a couple of things to consider.

  • Take all heading tags (H1 to H6) into account
  • Add ID attributes to the article headings where missing so they are not needed in the original markup
  • Handle multiple articles per page and make proper tables for each
  • Offer some settings for the wrapping jQuery selector, headline text and class name

The Code

Mostly the “handle multiple articles” aspect required special attention. How to set up a working jQuery selector will depend on the structure of your site’s markup – mine is based on WordPress’ Twenty Twelve default theme.
For the development I set up a pen on CodePen:

See the Pen JS-generated Table of Contents by Christian Templin (@cianty) on CodePen

Include JavaScript into WordPress

There are various ways for using JavaScript in WordPress. I put the code into a separate file custom.js and uploaded it to the blog’s folder. Then in footer.php the file is included right before the closing body tag:

<script src="http://code.jquery.com/jquery-1.10.1.min.js" type="text/javascript"></script>
<script type="text/javascript" src="<path to your file>/custom.js"></script>
</body>

Now the tables are automatically inserted into each article.

If you wanted you could easily include an enumeration by either using CSS counter-increment or the JavaScript variable that is already there in the function for the generation of the IDs.
For additional inspiration check out this pen by Joel Newcomer using the jQuery Waypoints plugin.

Turning Edit Pages into View Pages

When creating the front end for an Intrexx application you usually set up an edit page where the user can create new data records and edit them later on. Then, to display the data you will often want to have a dedicated view page for presentation. When the data tables are large, however, i.e. when they consist of many different fields, maintenance of these different edit and view pages becomes increasingly difficult and time-consuming. In this post I want to show how Intrexx edit pages can be reused for presentation purposes.

Input vs Output

It is often best to have similar if not identical layouts for the edit and view pages. Different arrangement and positioning of fields and sections between edit and view pages is likely to confuse users. Most of the time the nature of the data will lend itself to one specific way of presentation that feels intuitive and logical anyway. So when you find yourself rebuilding an edit page as a view page, replacing all input controls with display controls, it may be worthwhile to enhance the edit page so that it can be used for presentation.

The easiest way of depriving an edit page of its edit functionality is by removing all save buttons. Fortunately, Intrexx provides the Conditional display option for buttons so you can write some sort of logic that controls the rendering of the buttons using Velocity. This is particularly important because it can prevent the rendering of the save button on the server side. If you were simply hiding the save buttons with CSS one could easily hack the site by manipulating the markup with Dev Tools, Firebug and the like.

There are countless possibilities for determining whether a page should behave as an edit page or as a view page. The most obvious is probably the use of a request parameter that is set when the user opens the page by clicking entries of a certain table, such as an “Archive”, whereas another table “My Tasks” would open the edit perspective by providing a different value for the request parameter or just omitting it altogether. The problem with request parameters is that a savvy user could potentially (re)set them through the url.

Another thing to consider is user rights. While it is easy to assign access rights for the edit page only to an Intrexx group of editors you would have to be more careful if all the magic now was to happen in the same edit page for both editors and readers. Again, fortunately all the neccessary tools are there, with Velocity context objects such as the $AccessController. In fact, you should see this as an opportunity to carefully re-think the setup of your application’s pages with regards to security. Are your edit pages savely protected against access of users who must not edit the data? Consider a situation where edit/view perspective is determined by some sort of context such as a status field in the data record, so that e.g. “finished” items are shown in a table from where they can only be viewed while “open” items are shown in a different table that opens the edit page: What if a user explicitly enters the url using rq_TargetPageGuid? Would it be possible to access any edit page with any data record (by changing rq_RecId)? If you haven’t thought about these questions before chances are that your application becomes much safer if you only have one edit page where you consciously and explicitly control the behaviour.

Types of input controls in Intrexx

Once you made sure that there is no save button that shouldn’t be there, it is time to disable the page’s input controls. To do so it is probably a good idea to look at the markup and (JavaScript) functionality of the various Intrexx input controls. With jQuery being included in Intrexx by default the markup allows for a convenient way of iterating over the controls. The following table lists all relevant input controls as of Intrexx 6 along with their Intrexx names in both English and German and with their respective markup by which they can be identified:

Control type Selector
Button (Button)Schaltfläche (Schaltfläche) input[type="submit"]
Button (Text)Schaltfläche (Text) a, generic link with no recognizable attributes :(
Button (Image)Schaltfläche (Bild) a[role="button"]
Edit fieldEingabefeld input[type="text"]
Text areaTextfeld textarea
Option fieldOptionsfeld input[type="radio"]
CheckboxKontrollkästchen input[type="checkbox"]
Selection listAuswahlliste select
List fieldListenfeld select
Multiple selectionMehrfachauswahl div[id^="ID_distributioncontrol"], complex markup
Sortable listSortierbare Liste div[data-selectcontrol] or div[id^="ID_sortablelisteditcontrol"], complex markup based on ul
File selectionDateiauswahl input[type="file"]
Image selectionBildauswahl input[type="file"] (has accept="image/*")
TreeBaum div[data-selectcontrol] or div[id^="ID_treecontainer"], complex markup based on ul

Since the save functionality has been disabled on the server side we can probably ignore the three button types*. Basically what we end up with is two categories of input controls: Firstly, standard HTML form elements that can be easily targeted by their markup such as input, select and textarea; secondly, complex controls that consist of various elements that are difficult to identify and disable. Let’s deal with the easier ones first.

*If you do use buttons for non-saving purposes, don’t worry, though – we’ll look at that later on.

Disable standard form elements with jQuery

Writing a simple JavaScript function that makes use of jQuery to select all standard form elements of a page and disable them is not too hard. Especially since jQuery conveniently comes with the appropriate (non-CSS) :input selector already. Also make sure to use prop() instead of attr().

function disableInputControls(p_strWrapperId){
	if(!p_strWrapperId)return;
	$('#'+p_strWrapperId).find(':input').prop("disabled", true);
}

Note that it is important to provide a hook for the selector in form of the page id. Without specifying the parent element p_strWrapperId the selector would target all input elements on the current page, including the surrounding portal with its portal search field and button as well as other tooltip windows that may be open when the function executes.

This leaves us with three remaining complex controls still enabled. Let’s look at each of them:

Multiple selection: This control contains a wrapping DIV element that can’t really be targeted other than by its default class (DistributionControlHorizontal) or id (e.g. ID_distributioncontrol7C168C4E). Assuming that an application developer is much less likely to change the control’s name attribute (via the expert attributes tab in the Portal Manager), which determines the HTML id attribute, than they are likely to change the class, going with a selector like this is probably successful 99% of the time: div[id^="ID_distributioncontrol"]. But what to do with this element now? The control consists of two buttons for moving (i.e. selecting) entries from the left to the right and back (i.e. deselecting). These are inputs so they were already targeted by the jQuery selector above. However, the user can still double-click on entries of the lists in order to move them to the respective other list. Looking at the markup shows that this control is made up of two unordered lists.

Sortable list: The Sortable list is another control with complex functionality. Similarly to the Multiple selection it has a containing DIV element; this one with a custom attribute called data-selectcontrol. However, this attribute is far from suffiently distinct: the Multiple selection also has a descendant div with data-selectcontrol. A more reliable solution is probably, again, to work with the control’s id structure: div[id^="ID_sortablelisteditcontrol"].

Tree: Just like the Sortable list control there is a div[data-selectcontrol] element but it’s it’s probably better to go with div[id^="ID_treecontainer"]

So there are ways to find these controls within a page. The problem is because of their markup there is no reliable generic way to identify their parent elements. Often descendant elements have ids that are based on the parent id and have some sort of extension, like ID_distributioncontrol7C168C4E_selected_hidden. Depending on how the control is to be disabled this ambiguity can be a problem.

Disable elements with CSS

The often overlooked CSS property pointer-events allows to completely disable an HTML element and its children when set to none. Browser support for this property is decent enough, with only Internet Explorer spoiling the party. If you have to support IE though (like me), this is not an option. If it is, your function might look like this:

function disableInputControls(p_strWrapperId){
	if(!p_strWrapperId)return;
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').prop('disabled', true);
	l_ojPage.find('div[id^="ID_distributioncontrol"]').css('pointer-events','none'); //Multiple selection
	l_ojPage.find('div[data-selectcontrol]').css('pointer-events','none'); //Sortable list & Tree
}

And you’re done. Life could be so easy.

Disable elements with clicktrap DIV

A simple alternative to the CSS pointer-events approach could be to just create DIV elements and place them above the controls so that they function as a clicktrap for the mouse.

l_ojPage.find('div[id^="ID_distributioncontrol"]').wrap('<div style="position:relative;" />')
	.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');

Unfortunately there are several problems with this. While the aforementioned ambiguity of the selectors is no problem for the pointer-events solution, it is here: The above code messes with the layout as it is applied to child elements. To make things worse, the Sortable list control in my application is not even a DIV element – it is a TD!

<td valign="top" id="ID_sortablelisteditcontrol654224A2" colspan="12" rowspan="2">

Crazy! I have no idea why Intrexx thinks that this is sensible markup, but that is what is generated.

I did consider and experiment with a few other solutions such as creating more complex selectors to make sure the correct element is found or searching for registered controls in Intrexx’ internal JavaScript objects. Ultimately I found all this to be way too much for such a simple task. If you think about, how many and how often do you use these controls in your pages? The answer to this question led me to the following solution.

Disable additional elements

The Multiple selection, Sortable list and Tree control are so rarely used and complex that it makes little sense to explicitly cater to them in a generic disable function. They are so special that it is fair to assume that an application developer wishing to disable all input elements on an edit page can take care of these controls explicitly. Therefore the function is extended but another parameter that allows to pass the guids of those elements that should be disabled in addition to standard input elements.

function disableInputControls(p_strWrapperId, p_aExplicitInputs){
	if(!p_strWrapperId)return;
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').prop('disabled', true);
	
	var l = p_aExplicitInputs? p_aExplicitInputs.length : 0;
	for(var i=0; i<l; i++){
		var key = p_aExplicitInputs[i];
		key = getElement(key)? getElement(key).id : document.getElementById(key)? key : false;
		if(!key)continue;
		$('#'+key).filter('div').wrap('<div style="position:relative;" />')
			.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');
	}
}

To deal with TD parent elements I simply chose to filter for DIVs. Now this makes the selector ignore the Tree and Sortable List, which are both TD in my current page, but this is easily made up for by the fact that you can now group elements within a DIV container in the Portal Manager and pass the container’s guid to this function. Thus you can easily disable any section of the page, not just these specific controls.

I also threw in a bit of extra code to allow the passing of both guids and ids for identifying the elements.

Excluding Cancel and Close Buttons

When a page is opened as a modal tooltip it usually has some kind of Close button. Not the X button in the upper right corner but a proper button like the Cancel that usually sits next to Save. These buttons – Close and Cancel – should be excluded from the disable function. Luckily they can be identified rather easily: in Intrexx each button has a linktype. You can look up each button’s linktype in the expert attributes tab in the Portal Manager. The integer value of the linktype is determined by the button’s behaviour. If the button executes no action (such as save or delete) and does not open a new page, then its linktype is -2. In JavaScript you can check a button’s linktype like this:

getElement('C68...476').oUp.linkType == '-2'

So with a slight adjustment to the function we can no disable all inputs except for those buttons that do no real action.

// Disable standard input elements:
var l_ojPage = $('#'+p_strWrapperId);
l_ojPage.find(':input').each(function(){
	var l_oElem = getElement(getGuidById(this.id));
	if(l_oElem && l_oElem.oUp.linkType == '-2')return;
	$(this).prop('disabled', true);
});

Note the use of getGuidById(). This global function takes an id as a parameter and returns the corresponding Intrexx control’s guid. This allows to iterate over all input elements using the jQuery function and connecting the resulting elements to the Intrexx control JavaScript object.

The Finishing Touches and Food for Thought

The header of the edit page hopefully already consists of Velocity to communicate the page’s purpose to the user:
#if($DC.getRecId==-1)Create new Data#{else}Edit Data
If you use a request parameter (or other Velocity variable set in some way, like e.g. $bIsViewPage) to determine whether the edit page should behave as a view page then this should be incorporated into the title:
#if($DC.getRecId==-1)Create new Data#elseif($bIsViewPage)View Data#{else}Edit Data

When an edit page is meant to be used as a view page the idea behind disabled input elements changes. Normally, in an input form you have greyed out read-only fields that are clearly distinguishable from editable input elements. In a page layout that is purely meant for presentation of data this default styling makes little to no sense. You will probably want to create some CSS that makes the disabled elements on your edit page look nice and friendly (no grey background, no borders(?)). Maybe it is a good idea to add a certain class to the page container which can serve as a hook for the CSS selectors like .viewPage input{}.

I haven’t mentioned Data Pickers yet. They shouldn’t be a problem though, since the input fields as well as the Data Picker buttons themselves should be successfully disabled at this point.

Textareas may still have the little resize handle in the bottom right corner which gives the elements an iappropriately editable feel. You will probably want to style your textareas, irrespective of whether the page is in write or read-only mode.

I haven’t come across a use case yet, but theoretically it is possible to have complex filter on the edit page that should not be disabled, yet fall victim to the function. In that case you will probably have to manually enable them again after calling disableInputControls().

Summary

The JavaScript function disableInputControls() allows to disable all standard input elements of an Intrexx edit page. Additional elements and containers to disable can be passed via an optional parameter. This is especially neccessary for Intrexx’s complex controls such as Multiple selection, Sortable List and the input Tree.
In addition to calling this function you must ensure that save and delete buttons do not render in the HTML by using Velocity.
Here’s the final version of the function:

/**
	Disables all input elements (input, select, button, textarea).
	Usage: disableInputControls('ID_editpageC3FD4043',['1B3..8AC','ID_simplegroup1859F722']);
	@param p_aExplicitInputs - Optional array of guids or ids of additional elements to disable.
	These must belong DIV elements.
*/
function disableInputControls(p_strWrapperId, p_aExplicitInputs){
	if(!p_strWrapperId)return;
	
	// Disable standard input elements:
	var l_ojPage = $('#'+p_strWrapperId);
	l_ojPage.find(':input').each(function(){
		var l_oElem = getElement(getGuidById(this.id));
		if(l_oElem && l_oElem.oUp.linkType == '-2')return;
		$(this).prop('disabled', true);
	});
	
	// Explicitly disable elements from p_aExplicitInputs:
	var l = p_aExplicitInputs? p_aExplicitInputs.length : 0;
	for(var i=0; i<l; i++){
		var key = p_aExplicitInputs[i];
		key = getElement(key)? getElement(key).id : document.getElementById(key)? key : false;
		if(!key)continue;
		$('#'+key).filter('div').wrap('<div style="position:relative;" />')
			.parent().prepend('<div style="position:absolute;left:0;top:0;height:100%;width:100%;z-index:1;" />');
	}
}