A little background information:
This website is private project of mine. Initially setup about 9 years ago because a site like this, for the Belgium stock index BEL20, didn't exist. That time I programmed the website logic from scratch using php and mysql for the database. The website serves some Google Adsense ads (no, I can't quit my day job yet )
Although the frontend worked fine, the backend... well, actually phpmyadmin was my backend. The website was lacking a template engine like smarty, so updating the layout/design was a risky job because of the fact that the html was mixed with the php code.
After all these years I finally found the time (and courage) to migrate everything to CMS Made Simple.
Below I will share a little about some methods I used to get things working like I want it to.
My wishlist:
- Easier to enter/alter data
- Multi-language support
- Possibility to add more stock indexes
- As little duplicate data entry as possible
- Obviously powered by CMSMS
- ...
LISE
The main module used is LISE. It currently is the only 3rd-party module installed.
Using LISE I created three LISE instances (a LISE instance actually is a separate module which you can easily extend with many different types of field definitions); one for the list of Companies (categorized by stock index), one for Dividends, and one for Agenda items.
LISE features a very useful API and events (check Extensions > Event Manager). I'll give an example from my website:
Dividend has 3 relevant dates (in my case): date of approval, date of ex-coupon and date of payment. I also want to show those dates/events in the website's agenda which holds other (non-dividend) events too (agenda items 'live' in another LISE instance, remember). So when I save a new (or edited) dividend item I use a UDT (User Defined Tag) connected to the PostItemSave event to automatically generate the three agenda items (obviously the dates need to be filled in the dividend item). The agenda item properties will automatically be populated: the date, the type of event (payment,approval or ex-coupon), the company etc.
The example above is only one of the events and UDT's I use. In real I created a few UDTs and plugins to do all the stuff that needs to be done. A little knowledge about PHP and how to use an API is required to set it up like this.
Multi-language support
For the multi-language implementation I'm using some custom plugins (can be UDTs too), .htaccess rewriting and Smarty config files (more about that later).
The page hieracrchy is based on the language codes for the root parent's aliases:
Page template should check what is their root parent's alias. Check Rolf's blog for a method how to get it: https://www.cmscanbesimple.org/blog/mul ... page-alias
Note the url column; you can manually assign an url to a specific content object (e.g. a content page). This allows you to use a consistent naming scheme for the content aliases (language specific suffixes: contact-nl, contact-en etc.) and still use the desired url.
When the template 'knows' the root page alias it can be used to do useful things some of which are explained below.
But first a very simple auto detect language method implemented in .htaccess rewrite rules. I only use it to redirect vistors who are browsing to the main domain to a specific language. Note that French is commented out because it's not yet implemented. The rules are very simple: if the browser sends a Accept-Language header AND it starts with 'fr' (todo) or 'en' the visitor will be redirected to /fr or /en. In all other cases visitors will be redirected to /nl
Code: Select all
#RewriteCond %{HTTP:Accept-Language} ^fr [NC]
#RewriteRule ^$ /fr [L,R=301]
# English
RewriteCond %{HTTP:Accept-Language} ^en [NC]
RewriteRule ^$ /en [L,R=301]
# else redirect to the Dutch version
RewriteRule ^$ /nl [L,R=301]
Smarty config files allow you to create a plain text file to assign strings to variables. I'm using sections for the languages (but you can use separate files per language too if you want). Example config file (snippet):
Code: Select all
#global variables
#if a variable also exists in a section below that is loaded, it will be overwritten by the value in that section
url_twitter=https://www.twitter.com/whatever
url_facebook=https://www.facebook.com/whatever
### DUTCH ###
[nl]
amount=bedrag
date_approval=datum goedkeuring
date_excoupon=datum ex-dividend
date_payment=datum betaling
...
### ENGLISH ###
[en]
amount=amount
date_approval=approval date
date_excoupon=ex-coupon date
date_payment=payment date
...
### FRENCH ###
[fr]
amount=montant
...
Because of variable scope issues I load the config file and assign all variables to a Smarty array. If the visitor visits an English page the 'en' section is loaded. For Dutch pages the 'nl' section etc. Because of this method I don't need to create separate templates for each language. In smarty I can use the same variable names and the right text will be shown:
(the array with the variables from the config file is named $l)
Code: Select all
<dl class="dl-horizontal">
<dt>{$l.amount} <i class="fa fa-eur text-primary"></i></dt>
<dd>{$div->fielddefs['amount']->value|string_format:'%.3f'}</dd>
<dt>{$l.date_approval} <i class="fa fa-legal text-primary"></i></dt>
<dd>{$div->approvaldate|cms_date_format}</dd>
<dt>{$l.date_excoupon} <i class="fa fa-scissors text-primary"></i></dt>
<dd>{$div->excoupondate|cms_date_format}</dd>
<dt>{$l.date_payment} <i class="fa fa-money text-primary"></i></dt>
<dd>{$div->paymentdate|cms_date_format}</dd>
</dl>
Code: Select all
<dl class="dl-horizontal">
<dt>bedrag <i class="fa fa-eur text-primary"></i></dt>
<dd>2,000</dd>
<dt>datum goedkeuring <i class="fa fa-legal text-primary"></i></dt>
<dd>apr 25, 2018</dd>
<dt>datum ex-dividend <i class="fa fa-scissors text-primary"></i></dt>
<dd>apr 30, 2018</dd> <dt>datum betaling <i class="fa fa-money text-primary"></i></dt>
<dd>mei 3, 2018</dd>
</dl>
Code: Select all
<dl class="dl-horizontal">
<dt>amount <i class="fa fa-eur text-primary"></i></dt>
<dd>2.000</dd>
<dt>approval date <i class="fa fa-legal text-primary"></i></dt>
<dd>Apr 25, 2018</dd>
<dt>ex-coupon date <i class="fa fa-scissors text-primary"></i></dt>
<dd>Apr 30, 2018</dd>
<dt>payment date <i class="fa fa-money text-primary"></i></dt>
<dd>May 3, 2018</dd>
</dl>
It's also nice to use during development because you can start with defining variables at the top (above the first section). Those variables will be loaded regardless of the chosen section. If the website is almost finished you can start moving (or copying) those variables to the language sections and do the translations. I choose to prefix the VALUES of the top variables with an underscore so when checking the frontend I easily noticed if I forgot to translate a variable.
If you need to use a translated string that holds a variable value itself (e.g. a company name) it's possible to use it together with the php function sprintf.
Example:
Code: Select all
[en]
...
company_meta_description=Detailed information about the dividends of %s
Code: Select all
{$page_description=$l.company_meta_description|sprintf:$item->title scope=global}
'%s' will be replaced by the value of the title property of the Company item in this case.<meta name="description" content="Detailed information about the dividends of Proximus">
Note: to use the sprintf function you may need to loosen the security configuration for smarty!
I use only one LISE instance per language, so e.g. a Company item has an information text field for Dutch, one for English and one for French. All those fields are consistently named by language(e.g. info_nl, info_en, info_fr). Again to be able to use one Smarty template for all the languages:
Example snippet from a LISE detail template
Code: Select all
{* $lang holds the language code (nl, en or fr) and is globally defined at the beginning of the main page template *}
<h3>{$l.company_information}</h3>{* <-- print the header *}
{$item->fielddefs["info_$lang"]->value}{* <-- dynamically get the proper field from the LISE instance *}
This way it is possible to:
- inform the visitor that a specific piece of information is offered in another language
- set a lang attribute so browsers/search engines know about this different language
To get language specific urls I use a little code to search and replace some parts of the generated LISE urls. E.g. for Dutch it can be /nl/bedrijf/bel20/ab-inbev-nv/ and for English /en/company/bel20/ab-inbev-nv/.
In .htaccess I created some rewrite rules to rewrite those 'hand made' to urls CMSMS and LISE do understand. The rewrite rules contain (hard coded) the page id of the language specific target pages (LISE needs to know on which content page to display the item, and it's different for every language).
Note: If you want to have language specific aliases for an item I think you can't use this method but should create LISE instances for every language. Another advantage would be you can define the language specific url per LISE instance and set the target page there too. I think that is a better way to do it, so perhaps I will change it (in 9 years from now )
Navigation / Menu
To get the LISE items (companies) in the Navigator menu I used another custom plugin. If the Navigator template comes across a node (content page) with a specific value for extra1 (read about it here and in the help of the Navigator module) it will call the plugin which return 'nodes' based on the LISE company items (one of the extra page attributes holds the wanted LISE category). Those items will be used to create the submenu with companies.
Another little trick I used here is to add to the top of this submenu the parent page. The 'place' of the parent page itself is turned into a navigation 'header'. This is mainly useful for touch devices so they will be able to expand the menu without navigating to another page immediately.
Admin links on the frontend
Because the LISE instances (especially for dividend en agenda) each contain several hundreds of items it sometimes is hard to find a specific item in the backend list. Further, it sometimes happens I see missing information or something changes (like a date of an agenda item) and want to change it immediately . So again two plugins:
One checks if the website visitor happens to be logged in in the backend too (same browser). I just check for logged in because I'm the only admin user but it would be possible to check for specific group membership too.
Another plugin creates urls to specific admin actions of modules. Have a look at the screen shot below. Those red and orange buttons are only available when I'm logged in in the CMSMS admin backend too. The links will bring me immediately to the specific item to edit it (or add or delete). It even 'knows' when a agenda item is automatically created (as explained before) by creating a dividend item so in that case it will bring me to the dividend item to change it there (for consistency reasons).
It seems to work rather well but it should be noted that sometimes it does not. Probably when there has been a longer period of inactivity in the backend. I need to do some research but I guess that the backend session has expired then. Just a click in the backend and refresh of the frontend and things work well again. Though I wouldn't recommend to offer this option to regular customers like this yet.
Conclusion
First of all I'm happy with the result. Now it's much easier to add and edit my items and multi-language support has been added. This motivates me to update the information more often.
I needed to create a handful of custom plugins and UDTs to do some tasks (some of them are ok, some I'm not proud of ).
Everything is done without changing a single bit of code of the core nor the modules (so no hacks). Though I'm using a few database queries (to LISE tables) to make some processes more efficient than possible with the default options available.
The site isn't finished yet, I have some ideas to extend it, but the first, main step has been set (implementing CMSMS).
Hopefully some of you find the above description useful and of course it's an invitation to share your work, methods etc too.