I have designed a project using smarty and I have decided that the easiest way to add a transaltion system was to use gettext() because it was widely supported. I shortly discovered there were many issues with it which I have resolved bar one, scanning smarty .tpl files with Poedit. I am not going to do all my translations manually!!!
I managed to get quite far using poedit and messing with the various parsers but they were not 100% for me.
The goto plugin for smarty to add gettext support is smarty-gettext , a plugin that allows you to put text you want to translate inbetween some custom tags and then these will be translated into gettext() statements.
{t}....{/t}
Now Poedit will not find these tags, nor can you config custom scans for it in the software. You can however use external extrators to do this.
smarty-gettext has a command line parser that will scan your .tpl files and return correctly formatted .po and .mo files but this means you have to scan from the command line with this script, then you have to use Poedit to scan your PHP files which could get quite messy and then somehow merge the 2. So to that end I have manged to get the command line parser to function as a Poedit extractor. I will also go through the different parts of the process so you can perhaps build your own parser or utilise one that is already there. I have also found a few other Poedit parsers that might be of use and i will also list them later.
These instructions were written with gettext() in mind but because of caching issues with gettext() I decided to use motranslate. The instructions are exactly the same for both technologies except you need to add some Additional keywords for motranslate.
My advise is to follow these instructions carefully and then once you have the process working it is at that point you should alter it to your needs. These instructions are also for Windows and Xampp.
I will be using my file locations so everything matches up. I will also assume
When specifying a directory the script will only search within .tpl files and I need it to search with some .js files I have in my project. So edit the file and change:
// extensions of smarty files, used when going through a directory $extensions = array('tpl');
to
// extensions of smarty files, used when going through a directory $extensions = array('tpl', 'js');
- I added the 2 folders to the windows PATH so the files within can be found easily. You can probably get all of this working without adding stuff to the PATH but I would not reommend it. php.exe, msgcat.exe and msgmerge.exe all need to be found for this to work.
- You could probably get away with just adding the D:\websites\php tot he PATH but I found it neater to have these other files in their own folder.
- I choose the static versions for the gettext() package so I did not have to worry about if the .dll files were registered or could be found
- xammp does not need to be running
- make sure you do not have other version of gettext in the path otherwise the software can get confused
All the files and settings are inplace we now need to configure Poedit. Make sure you download the older v1.8.13 version because you can still see how all of the parsers are configured.
On the orginal Poedit (v1.8) you were able to select which parsers were enabled. These were all configured the same except for the language type and what files they scanned. In v2.0 this has been removed. Below is the PHP Extractor setupfrom v1.8.
I have been using Poedit v2 with success for QWcrm.
Instructions
Search all folders supplied by Poedit (recommended) php "D:\websites\php\tsmarty2c.php" -o %o %F Only searches in the folder 'themes' php "D:\websites\php\tsmarty2c.php" -o %o themes
D:\websites\htdocs\develop\qwcrm
cache\ nbproject\ libraries\ logs\ media\ themes\default\js\dhtmlcombo\ themes\default\js\tinymce\ themes\default\js\jscal2\jscal2.js themes\default\js\jscal2\unicode-letter.js themes\default\js\jscal2\css\
_gettext - This is for the function _gettext() __ - This is for the function __()
NB:
I will just explain a little here about this extraction process. All of the other Extractors are using xgettext() to scan files and return a correctly formatted .po file. xgettext() is dated and will not recognise a lot of languages and you can not add custom scans in to it.
Poedit extractor switches explained (This is useful so you can workout how to build you own parser.)
- -o - This is just a standard switch that is pass to the extractor. It is not specific to Poedit. However the xgettext() uses this to denote the output file and so does tsmarty2c.php
- %o - this is a placeholder for Poedit to swap out with the tempory .pot file location where changes are merged to.
- %k - this placeholder is to be able to pass custom function names to your script (or xgettext) to scan within for strings i.e. instead of gettext("translate me") you could scan for chicken("translate me") or football("translate me"). It is not mandatory to have this.
- %f - this is placeholder for the file list. I am not sure of the format but it is in a format that ngettext() will accept. All files are passed in 1 large list here. I can't work out if this is type, but to pass the list you need to use %F.
- --from-code=%c - this is always added on to the end of the command line and is to specify what charset the files houls be open as. I do not need this so have removed it. I thing it is mainly for xgettext. My scripts have been running with it attached though.
- NB: the instructions for Poedit for the extractors does not make sense. There is information left out.
So what happens with an extractor when you hit 'update from sources'
- poedit scans the project's folders (as per your rules) for allowed files and builds a list
- one of the following happens (not sure which)
- The files are passed as a single command line to an extractor that matches the file extensions (possible goes to multiple extractors if an extension is registered in more than 1 extractor)
- Each file is passed as a single command line to an extractor that matches the file extension (possible goes to multiple extractors if an extension is registered in more than 1 extractor)
- the extractor then parses these files and then merges the strings it finds to a temporary pot.file (using msgmerge.exe)
- the next extractor is run and those files are merged into the same temporary .pot file(using msgmerge.exe)
- once finished Poedit loads this temporary .pot file in to memory and you are returned to the main screen
- your changes have not been saved yet. For this you actually need to hit save to apply the changes to you .po file.
- Poedit will now build the .po and .mo files
Comodo Sandbox which is part of the comodo Internet Security (CIS) if running will cause the extraction process to fail because it blocks the PHP script. You need to disable the Comodo Sandbox before running the extraction. This might also be the case for other sandboxes and Firewalls.
So now run Poedit and extract the translations as outlined above by hitting the Update from sources menu item of click the Update button.
gettext() is a translation system written by GNU people and is present on many systems natively. This makes it a good standard to use. Gettext does have some issues especially on PHP and I will outline what I have found here with possible solutions
I now use motranslator which is based on the same technology as gettext because of the following:
- gettext translation file (.mo) is cached by the server and is never refreshed until you restart server, so changes you make will not be seen untill then. Not practical for shared webhosting.
- On Windows gettext will not allow translations to other than default language - There are some untested workarounds but they are messy (Custom Code, Thread Safe?, PHP as FastCGI not as a module)
- On Windows gettext will only read the directory that belongs to the default language meaning all translations need to go in the default local directory to allow translations.
Gettext Tutorials
Gettext Documentation
Gettext software
Misc documents
It is not straight forward to getting gettext() to workout of the box, especially when using xammp on windows. HEre I will address the issues I came across while trying to get translation to work. These issues probably also appear on diffrent setups.
When i was developing QWcrm on my windows xammp setup I would make changes to translation strings in th .PO file (via POedit) and these would not get reflected in my software. After a lot of research I discovered that when you run PHP as an Apache module the the getttext translation files upon first load are cached 'premanently' or for a time which I can not figure out. So to get around this I needed to find a way of refreshing the cache or turning it of for my development site.
Solutions I found
I will now outline my research below
Emptying of MO gettext cache without restarting apache links
These following issues can cause a real problem when devloping with gettext on windows. The main issue I came across is the localisation handling.
Issue
Solutions
You can write code to utilise the workaround outline above by address the locale issue. When trying to fix this do not forget about the caching issue above.
Links
xgettext is very particular about what it scans for, it seems to be syntax aware rather than just searching for the strings. i.e. if a line is remmed out it will ignore it.
once you have your project done you need to grab all of the strings and translate them, doing this manually would be difficult and you would miss strings. I outline the options I have found below:
Applications
Smarty TPL Scanning software
Poedit Links
Smarty Poedit Parsers (software) / and other parsers than can potentially be used
Poedit notes
Compiling all smarty templates
This section is involve in actually translating the strings in the smarty software.
Smarty translation links (various)
Smarty Software
tsmarty2c.php Command line examples
setup the software as per these instructions - configure the software, you dont need the poedit software for this to work.
In this article we will be discussing the compression of output from a php, not normal assets such as JS and css. Because the nature of PHP files is dynamic we have to use another method.
After needing to compress my PHP output for QWcrm I started researching on the internet after thinking it would just be a case of a couple of lines of code. What I discovered is there are several ways to compress PHP output and each has their pros and cons. I also found that some people were incorrectly using the wrong or outdated methods. Below I will go through each method I found and then I will sum up my thoughts at the end so you can easily workoout what method you want to use.
One other thing to note is that everyone goes on about how they gzip their content, but in these modern times there are 2 compression methos GZIP and DEFLATE, deflate being the newer method and can offer better compression.
I have discovered that on some server installs that the following code (in particular text/html) will compress PHP output. Obviously you have to have mod_deflate installed. Worth a try and add other file types as needed.
<IfModule mod_deflate.c> <IfModule mod_filter.c> AddOutputFilterByType DEFLATE text/html </IfModule> </IfModule>
Below is the normal way of compressing with deflate (gzip replacement) and the legacy gzip. I have added these for references only as it keeps coming up but will not actually compress PHP output but as a combined effect can help reduce the download footprint of the webpage and its assets.
Example 1 - Enable compression via .htaccess (mod_deflate)
This seems to be the prefered .htaccess compression method now because it gets better ratios.
<IfModule mod_deflate.c> <IfModule mod_filter.c> AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/x-javascript </IfModule> </IfModule>
Example 2 - Enable compression via .htaccess (mod_gzip)
http://www.awesomeinfolab.com/enable-gzip-compression/
I have never come across this method before in htaccess and might not work on all apache installs. I am thing of PHP as Fast-CGI/'Apache Module'
<ifModule mod_gzip.c> mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file .(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text/.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude mime ^image/.* mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* </ifModule>
Links
This takes advantage of Php's buffered output and buffers all output of the script until the Php engine has completed, and then runs all the output through a special function that gzip compresses it before sending it on to the browser.
Just place this at the very top of all your PHP pages and it will send gzip-compressed output to the browsers with the correct headers.
ob_start("ob_gzhandler");
The function basically says start buffering PHP content and tag it to says the outputted content should be gzipped. The procedure should also send the correct headers so the browser knows it is compressed. I also think that unless the browser has told the server that it supports compression that the content will be returned uncompressed. All modern browsers send the 'I support compression' headers.
Some say once the script is finished it will flush the buffer and output the content automatically and that is why you can get away with 1 line, however this article from magicmonster tells you to add the flush command at the end to flush the cache.
ob_end_flush();
The method mentioned above is quick and easy, but the downfalls are that it only works on Apache with mod_gzip and according to the Php manual this is not the preferred method for gzipping.
You can increase the compression ratio by altering the php.ini or add the following to the script before ob_start("ob_gzhandler")
ini_set('zlib.output_compression_level', 4);
Another example
<?php if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); else ob_start(); ?>
This example adds a check to see if the browser send the “Accept-Encoding: gzip” or “deflate” header before enabling the compression, however the buffer is still enabled even if compression is not required.
(From the Php manual at www.php.net)
note that using zlib.output_compression is preferred over ob_gzhandler().
Links
Create a buffer, fill the buffer, compress the buffer data and send the content to the client
This is similiar to ob_start("ob_gzhandler") but there is a little more to it.
Example 1
http://www.webcodingtech.com/php/gzip-compression.php
This example utilises the PHP buffer correctely and manual takes the buffered content, sends the compress data headers relevant to the compression method, then it compresses the blob before returning it to the buffer. The buffers is then dispaly or returns to the browser as compress content. I dont think this method is dependant on having Apache mod_gzip installed for the auto detection of the compression headers from the browser or to send the correct output headers.
This example has some issues an seems dated to use for compression. I think PHP has moved on. I would not use print as echo is quicker and I have no idea what print("\x1f\x8b\x08\x00\x00\x00\x00\x00"); does.
// Include this function on your pages function print_gzipped_page() { global $HTTP_ACCEPT_ENCODING; if( headers_sent() ){ $encoding = false; }elseif( strpos($HTTP_ACCEPT_ENCODING, 'x-gzip') !== false ){ $encoding = 'x-gzip'; }elseif( strpos($HTTP_ACCEPT_ENCODING,'gzip') !== false ){ $encoding = 'gzip'; }else{ $encoding = false; } if( $encoding ){ $contents = ob_get_contents(); ob_end_clean(); header('Content-Encoding: '.$encoding); print("\x1f\x8b\x08\x00\x00\x00\x00\x00"); $size = strlen($contents); $contents = gzcompress($contents, 9); $contents = substr($contents, 0, $size); print($contents); exit(); }else{ ob_end_flush(); exit(); } } // At the beginning of each page call these two functions ob_start(); ob_implicit_flush(0); // Then do everything you want to do on the page echo 'Hello World'; // Call this function to output everything as gzipped content. print_gzipped_page();
Links
This is the prefered method for gzipping over ob_gzhandler()
The zlib extension can be used to transparently compress PHP pages on-the-fly if the browser sends an “Accept-Encoding: gzip” or “deflate” header. Compression with zlib.output_compression seems to be disabled on most hosts by default, but can be enabled with a custom php.ini file:
The zlib extension is the undelying technology and the zlib.output_compression = On is a switch that enables transparent/invisible compression on all PHP content. The zlib extension is also the library that is used for other compressions such as ob_gzhandler. I would need to check which compressions operations were covered by it.
This method is installed on most servers but left of by default. It will automatically send the output back to the client's browser in a compressed form if the 'allow compressed content' header is sent with the page request. If enabled in the php.ini then no further coding is required in any php (or other) script, or for that matter any assets.
Enable via php.ini
Add or alter the following line in the php.ini
zlib.output_compression = On
Enable via a PHP script
If zlib.output_compresssion is disabled but installed, you can enable either by editing the php.ini (as above) or you can add the following in to your PHP script. This will only enable compression for the script it is included in. and you must add this code before any headers or output is sent from the script. You should also note that having this method of compression will cause errors if further compression methods are implemented in your scripts.
if (extension_loaded("zlib") && (ini_get("output_handler") != "ob_gzhandler")) { @ob_end_clean(); @ini_set("zlib.output_compression", 1); }
As you can see this simple script makes a few checks, that the libary is present and that ob_gzhandler() has not been set. This is perhaps optional depending on your script, the ob_end_clean() just makes sure any buffers are emptied, I am not sure this is needed either. The @ symbol just surpresses errors and again these could be removed
Links
There is a difference between these 3 functions even thought they all compress a blob you supply to them. However the concesous is to use gzencode() as it outputs in the correct format including the required checksums. This function is also supports both gzip and deflate compression algorithyms.
Joomla Example
I have included this to see how these guys do it as they have more experience than me. This process happens in 2 distinct sections, the first check to see if the gzip function is enabled whilst check to see if the server supports zlib compression and that ob_gzhandler has not already been set.
/** * Execute the application. * * @return void * * @since 3.2 * * From {Joomla}libraries/cms/application/cms.php */ /** * @package Joomla.Libraries * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ public function execute() { // Perform application routines. $this->doExecute(); // If we have an application document object, render it. if ($this->document instanceof JDocument) { // Render the application output. $this->render(); } // If gzip compression is enabled in configuration and the server is compliant, compress the output. if ($this->get('gzip') && !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler')) { $this->compress(); // Trigger the onAfterCompress event. $this->triggerEvent('onAfterCompress'); } // Send the application response. $this->respond(); // Trigger the onAfterRespond event. $this->triggerEvent('onAfterRespond'); }
The second section actually does the compression and further checking, and yes there is some duplication of these checks. This fucntion supports the use of gzip or deflate compression algorithyms which is great for compatability. If you look at the code you can see that gzencode() is used and that if performs compression on a single variable (blob) rather than a page. gzencode() is able to use both algorithyms where as the other 2 functions this section covers cannot.
The code has been compress but because of the nature of the function you have to manually send the 'Content-Encoding' header so the browser knows the payload is compressed and how it has been compressed.
/** * Checks the accept encoding of the browser and compresses the data before * sending it to the client if possible. * * @return void * * @since 11.3 * * From {Joomla}libraries/joomla/application/web.php */ /** * @package Joomla.Platform * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE */ protected function compress() { // Supported compression encodings. $supported = array( 'x-gzip' => 'gz', 'gzip' => 'gz', 'deflate' => 'deflate' ); // Get the supported encoding. $encodings = array_intersect($this->client->encodings, array_keys($supported)); // If no supported encoding is detected do nothing and return. if (empty($encodings)) { return; } // Verify that headers have not yet been sent, and that our connection is still alive. if ($this->checkHeadersSent() || !$this->checkConnectionAlive()) { return; } // Iterate through the encodings and attempt to compress the data using any found supported encodings. foreach ($encodings as $encoding) { if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) { // Verify that the server supports gzip compression before we attempt to gzip encode the data. // @codeCoverageIgnoreStart if (!extension_loaded('zlib') || ini_get('zlib.output_compression')) { continue; } // @codeCoverageIgnoreEnd // Attempt to gzip encode the data with an optimal level 4. $data = $this->getBody(); $gzdata = gzencode($data, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE); // If there was a problem encoding the data just try the next encoding scheme. // @codeCoverageIgnoreStart if ($gzdata === false) { continue; } // @codeCoverageIgnoreEnd // Set the encoding headers. $this->setHeader('Content-Encoding', $encoding); // Header will be removed at 4.0 if ($this->get('MetaVersion')) { $this->setHeader('X-Content-Encoded-By', 'Joomla'); } // Replace the output with the encoded data. $this->setBody($gzdata); // Compression complete, let's break out of the loop. break; } } }
So that is how Joomla does compression and it should be noted that this method is probably the most compatible way of doing compression.
Links
I wrote my own function bas heavily of the Joomla function to enable gzip. In QWcrm I was able to do this becaus emy page is stored in a single varible/blob that I can manipulate.
In the main index.php I have the following code
################################################ # Page Compression # ################################################ // Compress page and send correct compression headers if ($gzip == true && $VAR['theme'] !== 'print') { $BuildPage = compress_page_output($BuildPage); }
This is the function code stored in include.php
########################################### # Compress page output and send headers # ########################################### /** * Checks the accept encoding of the browser and compresses the data before * sending it to the client if possible. * * @return void * * @since 11.3 * * From {Joomla}libraries/joomla/application/web.php */ /** * @package Joomla.Platform * @subpackage Application * * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved. * @copyright Copyright (C) 2017 - Jon Brown / Quantumwarp.com * @license GNU General Public License version 2 or later; see LICENSE */ function compress_page_output($BuildPage) { // Supported compression encodings. $supported = array( 'x-gzip' => 'gz', 'gzip' => 'gz', 'deflate' => 'deflate' ); // Get the supported encoding. $encodings = array_intersect(browserSupportedCompressionEncodings(), array_keys($supported)); // If no supported encoding is detected do nothing and return. if (empty($encodings)) { return $BuildPage; } // Verify that headers have not yet been sent, and that our connection is still alive. if (headers_sent() || (connection_status() !== CONNECTION_NORMAL)) { return $BuildPage; } // Iterate through the encodings and attempt to compress the data using any found supported encodings. foreach ($encodings as $encoding) { if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate')) { // Verify that the server supports gzip compression before we attempt to gzip encode the data. if (!extension_loaded('zlib') || ini_get('zlib.output_compression')) { continue; } // Attempt to gzip encode the page with an optimal level 4. $gzBuildPage = gzencode($BuildPage, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE); // If there was a problem encoding the data just try the next encoding scheme. if ($gzBuildPage === false) { continue; } // Set the encoding headers. header("Content-Encoding: $encoding"); // Replace the output with the encoded data. return $gzBuildPage; } } } #################################################################### # Get the supported compression algorithms in the client browser # #################################################################### function browserSupportedCompressionEncodings() { return array_map('trim', (array) explode(',', $_SERVER['HTTP_ACCEPT_ENCODING'])); }
In future I will try and see if the mod_deflate option is working on other servers as this might be built into newer version of the module to treat PHP output as normal files and compress them.
This is just a simple article to references most of the simple ADOdb functions I use in QWcrm and what they do:
$smarty->assign('currency_sym', get_company_details($db));
I will use this function for the demo. This function just returns all of the company information or a single item.
function get_company_details($db, $item = null){ global $smarty; $sql = 'SELECT * FROM '.PRFX.'TABLE_COMPANY'; if(!$rs = $db->execute($sql)){ force_error_page($_GET['page'], 'database', __FILE__, __FUNCTION__, $db->ErrorMsg(), $sql, $smarty->get_template_vars('translate_system_include_error_message_function_'.__FUNCTION__.'_failed')); exit; } else { if($item === null){ return $rs->GetArray(); } else { return $rs->fields[$item]; } } }
I will be changing the line and listing the results
return $rs->GetArray();
$rs->GetArray();
Array ( [0] => Array ( [0] => QuantumWarp [NAME] => QuantumWarp [1] => 123456 [NUMBER] => 123456 [2] => The Gurking [ADDRESS] => The Gurking [3] => London [CITY] => London [4] => Greater London [STATE] => Greater London [5] => LA1 W10 [ZIP] => LA1 W10 [6] => AF [COUNTRY] => GB [7] => 123456 [PHONE] => 123456 [8] => 123456 [MOBILE] => 123456 [9] => 123456 [FAX] => 123456 [10] => [EMAIL] => [11] => £ [CURRENCY_SYMBOL] => £ [12] => GPB [CURRENCY_CODE] => GPB [13] => %d/%m/%Y [DATE_FORMAT] => %d/%m/%Y [14] => media/logo.png [LOGO] => media/logo.png [15] => [WWW] => [16] => 10 [OPENING_HOUR] => 10 [17] => 0 [OPENING_MINUTE] => 0 [18] => 17 [CLOSING_HOUR] => 17 [19] => 0 [CLOSING_MINUTE] => 0 [20] => 3.50 [TAX_RATE] => 3.50 [21] => <p>welcome</p> [WELCOME_MSG] => <p>welcome</p> [22] => <p>thanks</p> [INVOICE_MSG] => <p>thanks</p> ) )
$rs->GetRows();
$rs->GetAssoc();
Array ( [QuantumWarp] => Array ( [NAME] => QuantumWarp [0] => 123456 [NUMBER] => 123456 [1] => The Gurking [ADDRESS] => The Gurking [2] => London [CITY] => London [3] => Greater London [STATE] => Greater London [4] => LA1 W10 [ZIP] => LA1 W10 [5] => AF [COUNTRY] => GB [6] => 123456 [PHONE] => 123456 [7] => 123456 [MOBILE] => 123456 [8] => 123456 [FAX] => 123456 [9] => [EMAIL] => [10] => £ [CURRENCY_SYMBOL] => £ [11] => GPB [CURRENCY_CODE] => GPB [12] => %d/%m/%Y [DATE_FORMAT] => %d/%m/%Y [13] => media/logo.png [LOGO] => media/logo.png [14] => [WWW] => [15] => 10 [OPENING_HOUR] => 10 [16] => 0 [OPENING_MINUTE] => 0 [17] => 17 [CLOSING_HOUR] => 17 [18] => 0 [CLOSING_MINUTE] => 0 [19] => 3.50 [TAX_RATE] => 3.50 [20] => <p>welcome</p> [WELCOME_MSG] => <p>welcome</p> [21] => <p>thanks</p> [INVOICE_MSG] => <p>thanks</p> ) )
$rs->FetchRow();
Array ( [0] => QuantumWarp [NAME] => QuantumWarp [1] => 123456 [NUMBER] => 123456 [2] => The Gurking [ADDRESS] => The Gurking [3] => London [CITY] => London [4] => Greater London [STATE] => Greater London [5] => LA1 W10 [ZIP] => LA1 W10 [6] => AF [COUNTRY] => GB [7] => 123456 [PHONE] => 123456 [8] => 123456 [MOBILE] => 123456 [9] => 123456 [FAX] => 123456 [10] => [EMAIL] => [11] => £ [CURRENCY_SYMBOL] => £ [12] => GPB [CURRENCY_CODE] => GPB [13] => %d/%m/%Y [DATE_FORMAT] => %d/%m/%Y [14] => media/logo.png [LOGO] => media/logo.png [15] => [WWW] => [16] => 10 [OPENING_HOUR] => 10 [17] => 0 [OPENING_MINUTE] => 0 [18] => 17 [CLOSING_HOUR] => 17 [19] => 0 [CLOSING_MINUTE] => 0 [20] => 3.50 [TAX_RATE] => 3.50 [21] => <p>welcome</p> [WELCOME_MSG] => <p>welcome</p> [22] => <p>thanks</p> [INVOICE_MSG] => <p>thanks</p> )
$rs->GetRowAssoc();
Array ( [NAME] => QuantumWarp [NUMBER] => 123456 [ADDRESS] => The Gurking [CITY] => London [STATE] => Greater London [ZIP] => LA1 W10 [COUNTRY] => GB [PHONE] => 123456 [MOBILE] => 123456 [FAX] => 123456 [EMAIL] => [CURRENCY_SYMBOL] => £ [CURRENCY_CODE] => GPB [DATE_FORMAT] => %d/%m/%Y [LOGO] => media/logo.png [WWW] => [OPENING_HOUR] => 10 [OPENING_MINUTE] => 0 [CLOSING_HOUR] => 17 [CLOSING_MINUTE] => 0 [TAX_RATE] => 3.50 [WELCOME_MSG] => <p>welcome</p> [INVOICE_MSG] => <p>thanks</p> )
$rs->fields[$colname];
QuantumWarp
Muliple Records/Rows = $rs->GetArray(); This returns a 2 dimensional array which allows you to loop through the records which are also arrays.
Single Record/Row = $rs->GetRowAssoc(); - This returns a standard associative array.
Single Item from a column in a single Row = $rs->Fields[$colname];
We have all seen the terms and conditions pages and privacy policies on websites, most of us have not read them but they are an important part in protecting yourself from litigation from people on the internet and it helps set out a framework on how interactions between your website and the end user should occur. So you have made a website and you know you need to add them, but what now?
There are many different polices that I have come across so before you decide what you want on your website I should outline the main policies and what they are for below.
Terms and Conditions can be used as a blanket term or a specific policy. If you are running a website that provides pricing information, advice or any other service, you may want to consider adding a Terms and Conditions page or similiar page. This can absolve you of responsibility if anyone mistakenly uses your information for the wrong purposes, or wants to hold you liable for damages they have incurred by using your website or its information. Some other useful functions of a Terms and Conditions page is:
According to some, the name of the contract does not matter and that perhaps is why there are so many variations for the same thing, but read my thoughts below. You can call the contract what you want as it is a contract between you and the customer but I would stick with these rules because a good lawyer could say you tried to confuse the end user if the name was to different from what the contents purpose was.
The descriptions below are not the absoulte meaning of the titles because of the interchangability but I feel they are an apt description of what they are for.
The following articles all point towards them all being the same
Original before the internet this contract between you and the customer was called Terms and Conditions of Business which was then shortend to Terms and Conditions. This is what should be used for Bricks and Mortar business that primarily deal with a physical product. It should be noted that when you search for Terms and Conditions on Wikipedia that you are redirected to a page called Contractural term, which it probably is rather than a document name. Now we are in the age internet, people are still using Terms and Conditions to denote their agreement with the end user. I however feel that because you deliver things through a website, whether you deliver a physical product or not, maybe just a blog or advice, you are offering a service! This means that you should use Terms of Service. But what about Terms of Use I hear you say? Terms of Use is just another prose of Terms of Service instead of looking at it from the website or companies side you have looked at things from the user's point of view, or at least that is what the name implies. These terms are all interchangable in this modern world even thought I am not 100% convinced when you arbitrarily choose one of these terms it is the correct one, hence my musings here. The title must make sense to the policy it entitles.
It does now seem that Terms of Service is the most popular term name for websites. I have read some advice just to call the document Terms and not add any of the other ancillary words, I would perhaps refrain from that so the customer and laywers know what you meant. You can quite happily call the hyperlink Terms but give the document a proper title.
There are a couple of different parts to consider when you want to add legal policies to your website.
There are that many different variations used on websites all over the internet but no real standard on how to layout your 'Terms and Conditions' and 'Privacy Policy' and even how to actually layout out those policies. What I hope to achieve in this article is show you how to configure all these different parts based on your business model and website by following rules I set down in the following sections giving rise to a standard.
When filling in these documents consider using an email that is not your primary address so if spammers get hold of it you can chnage the email on the policies easily. I am not sure about the legalities of this and in fairness i do think it will cause any issue sunless you are in the middle of communication for an issue and even then the email address will be available. Use something like privacy2015@quantumwarp.com and then you can change the year if needed without killing of your main email address.
The hyperlink is the first thing a customer will look for on the home page when looking for the terms and conditions. In the USA the privacy policy law states that there should be a link on the homepage (if not all) with the word ‘Privacy’ prominently part of it pointing to the sites privacy policy. There does not seem to be a similar directive for 'terms and conditions' but we shall use the same model and even though this is a USA law there is no harm applying it to the rest of the world.
If in the UK/EU you need to select whether you want a single privacy page incorporating your cookies policy and privacy policy or if you want them as seperate pages. If you have lots of cookie information then maybe having a seperate cookies policy page.
This table will help you how decide how to build your hyperlinks for your Privacy Policy and Terms and Conditions pages link to your terms page. Select the relevant rows for your scenarion below, considering:
This will result in giving you the hyperlink that matches your ciscumstances.
Selection | Result | |||
Country |
Hyperlink Anchor Text |
Page Slug / SEF URL |
Notes |
|
1 |
All |
Terms & Conditions |
../terms |
All policies would be listed in their own right here i.e. ‘Terms of Service’. (i) single page with all listed (ii) a holding page with links to all of the polices (ideal if too large) – both will have paragraphs at the top saying they all form part of a single agreement Use ‘&’ instead of ‘and’ in the hyperlink text to make it shorter. This is the most common but is optional |
2 |
All |
Terms Of Service |
../terms |
Only use this if you need to be very specific. If you use this then i would recommend the resultant page must be titled 'Terms of Service' otherwise it could be confuding |
3 |
All |
Terms |
../terms |
Use this when there is not a lot of real estate |
|
||||
4 |
USA/Rest of World |
Privacy |
../privacy |
Use this when there is not a lot of real estate |
5 |
USA/Rest of World |
Privacy Policy |
../privacy |
|
6 |
UK/EU |
Privacy |
../privacy |
Use this when there is not a lot of real estate |
7 |
UK/EU |
Privacy Policy |
../privacy |
Do I need cookies in the link?) |
8 |
UK/EU |
Privacy & Cookies Policy Privacy & Cookies |
../privacy |
both policies on one page |
|
||||
9 |
UK/EU |
Privacy Policy |
../privacy |
separate page (opt) |
10 |
UK/EU |
Cookies Policy |
../cookies |
separate page (opt) |
Notes
My Selection
- Terms Hyperlink (option 1)
- 'Terms & Conditions' for the hyperlink anchor text
- ../terms for the page slug
- the use of ampersand keeps the link shorter
- This seems to be the most popular options and does not tie me to using ‘Terms of service’ on the ../terms page
- It allows me to change the terms at a later date without having to change the hyperlink
- It is well recognised in the USA and UK
- I have enough room for the full anchor text
- Privacy Hyperlink (option 8)
- 'Privacy & Cookies' for the hyperlink anchor text
- ../privacy for the page slug
- I have enough room for the full anchor text
- It is well recognised in the USA and UK
- I did not want a separate cookies page
- Cookies Hyperlink (n/a)
- I did not want a separate cookies page so this is not needed
We have identified your hyperlink and page slug for your selected scenario. We now need to establish the actual layout of the content, titles and sections which can be just as complex unless someone just tells you what they would do.
I have seperated this process out in to 2 sections, Terms and Conditions and Privacy Policy to keep things logical as they have different thought processes and are held on different pages. In this section we will deal with the Terms and Conditions page.
We now need to select the option by your type of business model because this has the biggest influence for the page name.
This table will help you decide how to name and layout your page titles for Terms and Conditions.
Selection | Result | |||
|
Type of Business |
<h1> / Primary Title / Page Title |
Rename TOS policy to ? and add it as a subthing (<h2> Section / hyperlink / clause ) to the main policy as defined by the page title |
Notes |
1 |
Non-interactive website |
Disclaimer |
x |
Add disclaimer only, you do not really need a 'Terms of Service'. You can use option 2 if you do want one. |
2 |
Interactive Website Only |
Terms of Service |
x |
All rules can be put in the TOS as clauses |
3 |
Website + e-commerce |
Terms of Service |
Terms of Website Use |
All rules can be put in the TOS as clauses |
4 |
Website + e-commerce + Bricks and Mortar |
Terms and Conditions / Terms of Service |
Terms of Website Use |
|
5 |
Bricks and Mortar only |
Terms and Conditions Terms and Conditions of Business Terms and Conditions of Sale |
x |
Notes
My Selection
Option 4
- The combined title prevent misunderstanding and utilses the 2 most common names for this contract
- It is universal and easy to understand
- The TOS can easily be expanded
- The website use terms still under the single policy are in a clear section
The link, page(s) and title(s) are now set. You have a couple of options that you can use to display your content not. These are the variations of layouts that you can apply to your terms page when you require more than a TOS, such as terms-of-sale which includes DSR (EU regulation, Distance Selling Regulation) to be accepted or displayed.
Example subthing names, these now come into play.
Subthings is the name I have given to the <h2> sections/hyperlinks/clauses reference points within the terms and conditions we are building here. You will use these containers once you have decided which you prefer
These are the available layouts and their pros/cons. This section is more about personal preference.
This is dealing with layout and sub title stuff, everything else is done.
Selection | Selection | Result | ||||
|
Terms page type |
Subthing policy type:
<h2>Sections Hyperlinks Clauses |
Example sites |
Notes |
||
1 |
1 page 1 policy |
Clauses |
x |
Probably not suitable if you are adding a lot of extra policies, you will need to make sure that all of the new clauses numbers match with the pre-exisiting policy, but ideal for small personal sites with interaction. |
||
2 |
1 page 1 policy |
<h2> Sections |
x |
All policy areas are added as <h2> sections under the main <h1> title of that page so they is 1 policy to agree too. There must be a maintained hierarchy You could add the sections as extra clauses in the Terms, but adding a <h2> section is cleaner when adding another policy so you don’t have to change all of the rerferences |
||
3 |
1 page 1 policy |
Hyperlinks |
All policy areas are added as sections under the main title of that page so they are 1 policy to agree too. There must be a maintained hierarchy. However, other policy terms can hyperlinked (ideal when they are long) with a paragraph at the beginning of the policy telling you of this. |
|||
4 |
1 page 1 policy |
Clauses <h2> Sections Hyperlinks |
x |
A combination of all 3 ways to add additional policies and conditions is possible but just be careful to keeps things organised. |
||
5 |
1 Index page Multiple policies |
hyperlinks |
When you have many terms and conditions you can list all of your terms and policies here as hyperlinks You might struggle having it agreed someone accepting these terms by pointing to this page. The TOS would be on its own page in this scenario. Paragraph saying all of these make up the terms and conditions? |
Notes
My Selection
Option 2
- single policy with multple sections is easy to manage
- can easyily be accepted as a single TOS
- easy to expand
We now have identified your hyperlink and page slug for your selected scenario. We now need to establish the actual layout of the content, titles and sections which can be just as complex unless someone just tells you what they would do.
I have seperated this process out in to 2 sections, Terms and Conditions and Privacy Policy to keep things logical as they have different thought processes and are held on different pages. In this section we will deal with the Privacy Policy page.
the layout of these polciies is much simplier. so the decision process is much simplier
This table will help you decide how to name and layout your page title(s) for the Cookies and Privacy policies. consider:
Selection | Result | |||
|
Type |
<h1> / Primary Title / Page Title |
<h2> Titles |
Notes |
1 |
USA/Rest of world |
Privacy Policy |
x |
x |
2 |
UK Single Page 1 policy |
Privacy and Cookies Policy |
x |
If you get a pre-done policy for the UK with Cookies as part of it, just fill in the information and you are done |
3 |
UK Single Page 1 policy 2 sections |
Privacy and Cookies Policy |
Privacy Cookies |
This allows you to get a good privacy policy yet use a cookie audit tool that generates it’s own cookie policy (atticat) and add them together easily. Also makes updating easy because you don’t have to merge them, just replace. |
4 |
UK Single Page 2 polices |
Privacy and Cookies Policies |
Privacy Policy Cookies Policy |
You might consider changing the hyperlink anchor text to ‘Privacy’ with this option. |
5 |
UK 2 Pages Privacy |
Privacy Policy |
x |
If your Privacy Policy mentions Cookies then add a link to your cookies page saying more info available and that the Cookies Policy is part of the Privacy Policy. |
6 |
UK 2 Pages Cookies |
Cookies Policy |
x |
x |
Notes
1.2 By using our website and agreeing to this policy, you consent to our use of cookies in accordance with the terms of our Cookies Policy.
My Selection
Option 3
- Single Page (UK)
- Less pages to manage
- I don’t have a lot of cookies on my site
- Less links on my homepage
- Google only has a single policy integrating cookies
- I am in the UK and need a cookies policy
- Single Policy 2 Sections (UK)
- 2 sections so I can easily manage updates to the policies
- I can use the atticat cookie audit tool to generate a cookie policies with all the reference cookies as needed rather than doing it manually.
- I also have a really good privacy policy where I can remove the cookie section
- I am in the UK and need a cookies policy
- I removed the cookie clauses/section from my Privacy Policy
- I modified a clause (1.2) that says by accepting the privacy policy I also accepted the cookies policy. This is required so the cookies policy is refrenced in the privacy policy.
- only had to change the title heirarchy slightly
Terms and conditions are usually very specific to your website and sometimes your country and state. There is a lot of common statements such as liability and do not hack my website. I have use the phrase Terms and Conditions throughout this article to loosely reference this section. As you know many websites call their terms different things and then these terms might include more things that the Terms of Service. In the tables above you have decided what actuall polices you want on your site so i will try and make the following instructions as general as possible so the workflow is the same (or as close to) for all options.
To start with these are the recommended sources. I will refer to the following documents as Terms of Service
For more sources see the links section, for both generators and static documents.
Once you have the Terms of Service by filling in the document by following the instructions or by using the generator wizard
Optional
We now have the page created for your privacy policy and the titles we are going to use but we need content. Do not just go and copy and paste content from the internet as this violates copyright and the privacy policy would most likely not be correct for your website. There are many websites where you can get privacy policies for your website legally but I will outline the recommend ones below:
For more sources see the links section, for both generators and static documents.
Once you have the Privacy Policy by filling in the document by following the instructions or by using the generator wizard
If you are in the UK or the EU you need a Cookies Policy. You can either have this on the same page as the privacy policy as a section or a seperate policy or it can be on its own page. With any of these options the process is the same except how you alter the page and section titles as required.
For more sources see the links section, for both generators and static documents.
The DMCA allows communities to be protected from users posting copyrighted materials on the site as long as that website operates a DMCA policy to remove the content as per the rules they set down. It might be an obligation to show this via a page with a DMCA decalration on it. I am not sure whether is 100% required but it doe snot harm to have it on just incase.
If you operate a website or blog where you have member-posted content, you need to have a DMCA Takedown Notice on your website to help protect yourself from both copyright holders and your members.
Firstly aquire a policy:
For more sources see the links section, for both generators and static documents.
Now follow these simple instructions:
If you do receive a DMCA Takedown request, just take the material down straight away. These companies will have a lot money and resources than you.
My Selection
- DisclaimerTemplate.com
- really easy to edit
- not sure if it 100% compatible with UK law but it cannot harm to say you will remove the dodgy content
You have now added terms and conditions to your website with a decent organisation standard and hopefully some really good content. I would encourage to review your terms and conditions and privacy policy on a regular basis to make sure you are still compliant. If you are a good sized company, and you have completed this process perhaps get a lawyer to check everything out or supply some specific terms.
These are my instructions to get the Terms and Conditions and Privacy Policy I have used on this site from SEQ Legal, how to lay them out and why.
Firstly these instructions are my interactive website based in the UK.
Resources I used
Editing the SEQ Documents
This process should be done for all of the SEQ documents you are going to use on your site.
Once edited, we no need to transfer this into a html document/web page
Create page
Adding Privacy section/policy
Adding Cookies section/policy
1.2 By using our website and agreeing to this policy, you consent to our use of cookies in accordance with the terms of our Cookies Policy.
I installed the Cookie Consent ATOM from inspire theme for getting cookie consent notification.
I created a page as per the instructions above but added the following sentence (with hyperlink) so as to facilitate easier contact. I am not sure if this breaks the contract in any way but I would like the least amount of hassle, so i would just remove the offending content.
You may contact us via the contact form so we can deal with this issue.
I added the following clause so it is easier at a later date to expand my Terms & Conditions / Terms of Service. This statement also get the user to acknowledge that hyperlinked sections are part of the TOS.
This website is operated by Bob Marley (referred to as "QuantumWarp/we/our/us"). As user of this website (referred to as "you/your") you acknowledge that any use of this website including any transactions you make ("use/using") is subject to our terms and conditions below (which includes any other important hyper-linked sections e.g. Privacy & Cookies Policy.
The following policies form the Terms of Service, this is the contract between you and us for using our website or services and the purchasing of products or services.
Atticat Cookies Policy Generator
SEQ Privacy Policy
SEQ Terms and condition of Use
After building your Terms of Service / Terms and Conditions you might find that you need to add a few extra options, policies or terms that are not covered in my article but none the less are very useful. I will outline the ones I have come across here:
The following statement adds implicit acceptance of tetms by just usign the site and it also denotes hyperlinked sections are included in the terms by hyperlinking sections and further makes this clear by giving a short list of examples.
You can of course alter this to just promote either Implicit Consent or Hyperlinked Sections
This website is operated by Bob Marley (referred to as "QuantumWarp/we/our/us"). As user of this website (referred to as "you/your") you acknowledge that any use of this website including any transactions you make ("use/using") is subject to our terms and conditions below (which includes any other important hyper-linked sections e.g. How to use this website, Returns and refunds, and Privacy policy. In addition, you will find other useful information within Customer services. Please:
- read through these terms and conditions carefully before using this website.
- print a copy for future reference.
- also read our Privacy policy section regarding your personal information.
Not all Terms of Service will come with a clause like this or indeed any e-commerce clauses. This clause is a vehicle to be able to attach other terms and conditions to your primary Terms of Service document and still allow end users to explicitly except it. You can if needed, also hyperlink 'terms and conditions of supply' and/or alter the name of the document being included. You are also not limited to adding just this one clause, you can add further clauses like this or modified clauses following the same basic layout.
Transactions concluded through our site
Contracts for the supply of [goods OR services OR information] formed through our site or as a result of visits made by you are governed by our terms and conditions of supply.
If your company is located in the USA and/or your website is hosted in the USA (not 100% about this) then you need to comply with their rules and laws. I am not sure how international law matters are handled in reguards to this as to which regions laws are applied. These extra rules are mailny to do with the Privacy Policy, I am not sure if there are any specific changes required to the Terms of Service.
To make things easy you could add compliance for all of the laws and polices that are required for the USA (including the California ones) and this should not adversely you current privacy policy as these extra policies are usually on top of what is already there.
The extra Privacy Rules are:
You can read my 'Privacy Policy Notes' for further information or just use Google as these are well known rules. My advice would be to generate a Privacy Policy with freeprivacypolicy.com as this is upto date with all of the rules and is USA centric.
In this section I will add the notes I made but did not need to go in the instructions or elsewhere in this article.
Dont use your primary email for these online policy documents just incase spammers get hold of it, use something like privacy2015@quantumwarp.com and then you can change the year if needed without killing of your main email address.
I did these notes whilst using the freeprivacypolicy.com generator to make my Privacy Policy document with it.
Acceptance of terms was a major part of this article and now I want to explain a little more.
There are 2 types of acceptance:
There might be a difference between countries and then depending on what the terms are, you might be required to get explicit consent for the terms to of been deemed as accepted. There is no harm is having the terms implicitely accepted like Argos and then explicitely accepted by the use of a checkbox during registration to your site. This should cover both aspects.
I have read that in American (USA) courts unless a click is made (i.e. accept the terms and conditions checkbox) the acceptance is not likely to be upheld. This is one reason I am striving with these rules so that acceptance is upheld for both UK and US law. There might be times where implicit terms are accepted in the USA but i do not have any example sites for that.
Enable Acceptance of TOS in Joomla
Joomla is my platform of choice but this particular feature is hidden, Joomla enabling TOS articles:
I have not come across a plugin that will allow me to get users to re-accept the terms and conditions, but this would be useful, also Joomla calls it's terms, terms of use but when you sign up to Joomla it says accept Terms of service
Why a single page is important?
This is another central point of this article was to create rules for laying out the content so that all systems were able to accept the terms. What do I mean with this statement?
Most CMS systems such as Joomla or Wordpress have facilites to accept the Terms of Service (TOS) but you will only have the ability to select one page of terms. This is where my terms rules come into play as all of the different options allowing you to make a single page of terms that can be selected and then accepted. A single page allows you to get explicit consent for all of the terms you require.
The basic rule is that the Terms of Service page should clearly reference all other required terms and policies by hyperlinks with a paragraph saying to the end user that this is the case or that all of the other policies should be part of the singular Terms of Service policy.
Do you need to click to accept terms of sale?
If digital you cn just make this part of your TOS and then is hould not be an issue. When considering Bricks and Mortar businesses I think that you would need to reference it on your invoices and it would be Implicit consent you get because they would get the terms on the invoice after paying or could look them up on your website.
Twitch is an excellent example of a large site and how it handles it slegal documents so that is why I have given it a special mention here. Blow I will outline the main points:
When using the Twitch Services, you may be subject to any additional posted guidelines or rules applicable to specific services and features that may be posted online from time to time (the “Guidelines”). One example is Twitch's Community Guidelines. Twitch may also offer certain paid services, which are subject to the Twitch Terms of Sale as well as any additional terms or conditions that are disclosed to you in connection with such services. All such terms and guidelines are incorporated into these Terms of Service by reference.
By clicking Sign Up, you are indicating that you have read and agree to the Terms of Service and Privacy Policy
The following are links that I helped me figure out my rules and how everything worked.
If you really need to write your own terms and condtions then have a look through these guides.
Some people get confused between these two documents, I hope these helps.
Large companies are always changing their terms and conditions and these sites help you see what the terms mean and when they change.
There are some apps out there that you can use to build up a Cookie Policy along with what cookies are actually used. I am not 100% you have to do this anymore.
For all of us that do not have lots of money or are not lawyers these generators will be good enough, just give the content a read over once they are created. Lawyers want you to scare you in to spending money that you do not have. The internet is great you can get a document and print it out without ever needed a laywer. When you do earn enought money to warrant the laywer fees (or you can afford it ) then you should get one but until then use a generator.
Some of these generators are by well respected companies (ie. shopify) and if they were complete bollocks they would not offer them. It is true that a generic terms and conditions cannot cover all situations, btu most of them.
You should check the generated content as a lot of them are USA centric and need adapting for the EU and UK, in either case it is always good to read the output to make they are correct for your situation.
Some of the free sites give you free terms and consitions/privacy policys etc.. but leave out vital parts of the contract unless you pay. Some of these terms left out make them almost pointless so I have added these under paid.
Some websites offer excellent templates, free and paid, that require you to manually alter them giving you the same efect as a generator.
I used these to research this article and looked at how these lot did things.
While doing my research I had to assess a couple of sites and their products.
SEQ Legal
UKWADA
Their documents are only available if you are a member
Docular (free)
I looked into how prestashop handled acceptance of terms and here are my findings.
Terms of service I agree to the terms of service and will adhere to them unconditionally. (Read the Terms of Service) which teakes you to the 'terms and conditions of use' this sort of implies 1 terms and conditions page and only accepted when a purchase is made. perhaps a european thing.
I needed a hidden Gantry section so I could publish my Javascripts and modal code without creating a load of blanks space.
Solution
/* Hide Hidden Section - So i can paste scripts into it without adding blank space */ #g-hidden { height: 0px; }
So far it works but I have not extensively tested it.
Q: I can only disable comments via the options menu. There does not seem to be anyway to control it. How can I restrict comments to registered users?
A: There is no particular option for this case when using the built-in RSFeedback! commenting system. However, you can achieve this through template overrides. This procedure is explained here.
The file you need to perform template overrides is located under this path:
/components/com_rsfeedback/views/feedback/tmpl/default.php
Open the duplicate used for template overrides and search for:
<?php echo $this->comments->form;?>
Replace the above line with:
<?php $user = JFactory::getUser(); if($user->id != 0) { ?> <?php echo $this->comments->form;?> <?php } ?>
This article will go through a pratical example of adding Komento (v3.0.4) to a component, in this case RSFeedback (v1.5.13). I will cover the various aspects of the Koneto Plugin structure and then go into the actual integration and how all of these parts interact.
These are not easy to find unless you are looking for them but combined they really helped me make this article and my plugin.
This is my upgraded com_sample.php and I highly recommend reading through it as it explains alot of things in context.
<?php /** * @package Komento * @copyright Copyright (C) 2010 - 2016 Stack Ideas Sdn Bhd. All rights reserved. * @copyright Copyright (C) 2017 Jon Brown @ QuantumWarp.com * @license GNU/GPL, see LICENSE.php * Komento is free software. This version may have been modified pursuant * to the GNU General Public License, and as distributed it includes or * is derivative of works licensed under the GNU General Public License or * other free or open source software licenses. * See COPYRIGHT.php for copyright notices and details. */ // No direct access defined('_JEXEC') or die('Restricted access'); // Always load abstract class - This file also includes extra information about the functions here and the advanced functions not covered //require_once( JPATH_ROOT . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_komento' . DIRECTORY_SEPARATOR . 'komento_plugins' . DIRECTORY_SEPARATOR .'abstract.php' ); require_once( JPATH_ROOT . '/components/com_komento/komento_plugins/abstract.php' ); class KomentoComsample extends KomentoExtension { /****************************************************** * * START * [BASIC FUNCTIONS (METHODS)] * These functions are mandatory * ******************************************************/ // This property (object) stores all the required properties by Komento public $_item; // This property (array) stores all the key mappings of the required item properties to map from Komento's default key to your component's custom key public $_map = array( // not needed with custom getContentId() 'id' => 'id_field', // not needed with custom getContentTitle() 'title' => 'title_field', // not needed with custom getContentHits() 'hits' => 'hits_field', // not needed with custom getAuthorId() 'created_by' => 'created_by_field', // not needed with custom getCategoryId() 'catid' => 'catid_field', // not needed with custom getContentPermalink() 'permalink' => 'permalink_field' ); // Constructor - Add all required files for your component here and run its constructor public function __construct( $component ) { // Load all required files by component // $this->addFile( your component's files ); // $this->addFile(JPATH_ADMINISTRATOR . '/components/com_sample/config.php'); // This must be left at the end of this constructor function parent::__construct( $component ); } // This method should load the article's main properties based on article ID public function load( $cid ) { static $instances = array(); if( !isset( $instances[$cid] ) ) { // populate $this->_item with: // id_field // title_field // hits_field // created_by_field // catid_field // permalink_field // Create a Database Object $db = KT::getDBO(); // Create SQL query to load a single article $query = 'SELECT `id_field`, `title_field`, `hits_field`, `created_by_field`, `catid_field` FROM `#__ARTICLE_TABLE` WHERE `ARTICLE_ID` = ' . $db->quote( $cid ); $db->setQuery( $query ); // Run the single article query and if there are no objects to load call the onLoadArticleError event if( !$this->_item = $db->loadObject() ) { return $this->onLoadArticleError( $cid ); } // Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink_field = $this->prepareLink( $this->_item->permalink_field ); $instances[$cid] = $this->_item; } $this->_item = $instances[$cid]; return $this; } // This method should load all the article IDs filtered by category IDs public function getContentIds( $categories = '' ) { // Create a Database Object $db = KT::getDBO(); // Make sure the query is empty $query = ''; // If no categories are supplied then load all article IDs if( empty( $categories ) ) { $query = 'SELECT `id_field` FROM `#__ARTICLE_TABLE` ORDER BY `id_field`'; } // If categories are supplied then load all article IDs for articles belonging to those categories else { if( is_array( $categories ) ) { $categories = implode( ',', $categories ); } $query = 'SELECT `id_field` FROM `#__ARTICLE_TABLE` WHERE `catid_field` IN (' . $categories . ') ORDER BY `id_field`'; } // Run the query and return the results as an array $db->setQuery( $query ); return $db->loadResultArray(); } // This method should load all the category IDs of the component // Make sure you select 'Single Level' or 'Nested' categories public function getCategories() { // Create a Database Object $db = KT::getDBO(); // Single Level Categories //$query = 'SELECT `id`, `title` FROM `#__CATEGORY_TABLE`'; // Nested Categories $query = 'SELECT `id`, `title`, `level`, `parent_id` FROM `#__CATEGORY_TABLE`'; // Run query and return categories $db->setQuery( $query ); $categories = $db->loadObjectList(); // Populate category tree for Komento Admin Integration Tab for this plugin (optional) // This is used in Komento where you select which categories the plugin should be active on etc... foreach( $categories as &$row ) { // Single Level Categories //$row->level = 0; // Nested Categories $repeat = ( $row->level - 1 >= 0 ) ? $row->level - 1 : 0; // Build and Add the Category Tree entry $row->treename = str_repeat( '.   ', $repeat ) . ( $row->level - 1 > 0 ? '|_ ' : '' ) . $row->title; } return $categories; } // This method lets Komento know if this is the front page or category layout (Determine if is listing view) public function isListingView() { $views = array('featured', 'category', 'categories', 'archive', 'frontpage' ); return in_array(JRequest::getCmd('view'), $views); } // This method lets Komento know if this is the page that the comment form should be displayed on (Determine if is entry view) public function isEntryView() { return JRequest::getCmd('view') == 'article'; } // This method is the main method that appends Komento on the article public function onExecute( &$article, $html, $view, $options = array() ) { // $html is the html content generated by komento (includes listing and form) // Select 1 of the following outputs // This appends the HTML to the article object //$article->text .= $html; //return; // Return the Komento HTML code return $html; } /****************************************************** * * END * [BASIC FUNCTIONS (METHODS)] * ******************************************************/ }
This is the actual Komento plugin for RSFeedback and is very useful for comparing with my QW_com_sample.php to see what the differences are.
<?php /** * @package Komento * @copyright Copyright (C) 2010 - 2016 Stack Ideas Sdn Bhd. All rights reserved. * @copyright Copyright (C) 2017 Jon Brown @ QuantumWarp.com * @license GNU/GPL, see LICENSE.php * Komento is free software. This version may have been modified pursuant * to the GNU General Public License, and as distributed it includes or * is derivative of works licensed under the GNU General Public License or * other free or open source software licenses. * See COPYRIGHT.php for copyright notices and details. */ // No direct access defined('_JEXEC') or die('Restricted access'); // Always load abstract class - This file also includes extra information about the functions here and the advanced functions not covered //require_once( JPATH_ROOT . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_komento' . DIRECTORY_SEPARATOR . 'komento_plugins' . DIRECTORY_SEPARATOR .'abstract.php' ); require_once( JPATH_ROOT . '/components/com_komento/komento_plugins/abstract.php' ); class KomentoComrsfeedback extends KomentoExtension { /****************************************************** * * START * [BASIC FUNCTIONS (METHODS)] * These functions are mandatory * ******************************************************/ // This property (object) stores all the required properties by Komento public $_item; // This property (array) stores all the key mappings of the required item properties to map from Komento's default key to your component's custom key public $_map = array( // not needed with custom getContentId() 'id' => 'id', // not needed with custom getContentTitle() 'title' => 'title', // not needed with custom getContentHits() 'hits' => 'hits', // not needed with custom getAuthorId() 'created_by' => 'user_id', // not needed with custom getCategoryId() 'catid' => 'cat_id', // not needed with custom getContentPermalink() 'permalink' => 'permalink' ); // Constructor - Add all required files for your component here and run its constructor public function __construct( $component ) { parent::__construct( $component ); } // This method should load the article's main properties based on article ID public function load( $cid ) { static $instances = array(); if( !isset( $instances[$cid] ) ) { // populate $this->_item with: // id_field // title_field // hits_field // created_by_field // catid_field // permalink_field // Create a Database Object $db = KT::getDBO(); // Create SQL query to load a single article $query = 'SELECT `id`, `title`, `hits`, `user_id`, `cat_id` FROM `#__rsfeedback_feedbacks` WHERE `id` = ' . $db->quote( $cid ); $db->setQuery( $query ); // Run the single article query and if there are no objects to load call the onLoadArticleError event if( !$this->_item = $db->loadObject() ) { return $this->onLoadArticleError( $cid ); } // Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink = $this->prepareLink( $this->_item->permalink ); $instances[$cid] = $this->_item; } $this->_item = $instances[$cid]; return $this; } // This method should load all the article IDs filtered by category IDs public function getContentIds( $categories = '' ) { // Create a Database Object $db = KT::getDBO(); // Make sure the query is empty $query = ''; // If no categories are supplied then load all article IDs if( empty( $categories ) ) { $query = 'SELECT `id` FROM `#__rsfeedback_feedbacks` ORDER BY `id`'; } // If categories are supplied then load all article IDs for articles belonging to those categories else { if( is_array( $categories ) ) { $categories = implode( ',', $categories ); } $query = 'SELECT `id` FROM `#__rsfeedback_feedbacks` WHERE `cat_id` IN (' . $categories . ') ORDER BY `id`'; } // Run the query and return the results as an array $db->setQuery( $query ); return $db->loadResultArray(); } // This method should load all the category IDs of the component // Make sure you select 'Single Level' or 'Nested' categories public function getCategories() { // Create a Database Object $db = KT::getDBO(); // Single Level Categories $query = 'SELECT `id`, `name` FROM `#__rsfeedback_categories`'; // Run query and return categories $db->setQuery( $query ); $categories = $db->loadObjectList(); // Populate category tree for Komento Admin Integration Tab for this plugin (optional) // This is used in Komento where you select which categories the plugin should be active on etc... foreach( $categories as &$row ) { // Single Level Categories $row->level = 0; // Build and Add the Category Tree entry $row->treename = str_repeat( '.   ', $repeat ) . ( $row->level - 1 > 0 ? '|_ ' : '' ) . $row->name; } return $categories; } // This method lets Komento know if this is the front page or category layout (Determine if is listing view) public function isListingView() { $views = array('category', 'categories', 'feedbacks'); return in_array(JRequest::getCmd('view'), $views); } // This method lets Komento know if this is the page that the comment form should be displayed on (Determine if is entry view) public function isEntryView() { return JRequest::getCmd('view') == 'feedback'; } // This method is the main method that appends Komento on the article public function onExecute( &$article, $html, $view, $options = array() ) { // $html is the html content generated by komento (includes listing and form) // Return the Komento HTML code return $html; } /****************************************************** * * END * [BASIC FUNCTIONS (METHODS)] * ******************************************************/ }
If you have gone through the resources above (recommend) you will see that the functions are grouped into:
The plugin can also be placed into either of the following locations and note that the name is changed depending where you put it.
The objects declared at the top of the plugin $_item and $_map are self explanatory.
I will now go through the functions in the order they appear in the integration plugin. My notes will give you more of a practical insight into how they work. where an example is needed I will use my RSFeedback itegration code.
The QW_com_sample.php is fully annotated so if I missed anything just use that as a reference.
This function is where you include and files/dependencies that you need from the component, if any. This function then runs KT class constructor with the component name as a option so it has the relevant objects and resources for that component.
The parent::__construct( $component ); must be run at the end of the function so all the relevant assets have been called before it is intialised.
This function loads a single article into the $_item object using the mappings we defined in $_map.
This is where the configuration fun begins. There are several parts to this function that need to be dealt with.
QW Sample Version
// This method should load the article's main properties based on article ID public function load( $cid ) { static $instances = array(); if( !isset( $instances[$cid] ) ) { // populate $this->_item with: // id_field // title_field // hits_field // created_by_field // catid_field // permalink_field // Create a Database Object $db = KT::getDBO(); // Create SQL query to load a single article $query = 'SELECT `id_field`, `title_field`, `hits_field`, `created_by_field`, `catid_field` FROM `#__ARTICLE_TABLE` WHERE `ARTICLE_ID` = ' . $db->quote( $cid ); $db->setQuery( $query ); // Run the single article query and if there are no objects to load call the onLoadArticleError event if( !$this->_item = $db->loadObject() ) { return $this->onLoadArticleError( $cid ); } // Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink_field = $this->prepareLink( $this->_item->permalink_field ); $instances[$cid] = $this->_item; } $this->_item = $instances[$cid]; return $this; }
Create SQL query to load a single article
This query needs to be changed to load a single articles record for the components database. You can change the field names to match the components which can be found by looking at your database, locating the article table for your component and then rading the fild names from the top.
// Create SQL query to load a single article $query = 'SELECT `id_field`, `title_field`, `hits_field`, `created_by_field`, `catid_field` FROM `#__ARTICLE_TABLE` WHERE `ARTICLE_ID` = ' . $db->quote( $cid ); $db->setQuery( $query );
So in the case of RSFeedback the code above will be changed to
// Create SQL query to load a single article $query = 'SELECT `id`, `title`, `hits`, `user_id`, `cat_id` FROM `#__rsfeedback_feedbacks` WHERE `id` = ' . $db->quote( $cid ); $db->setQuery( $query );
You will note that #__ is a Joomla substitution string for the table prefix and is absolutely needed. You now have configured the article to be loaded as needed except for the permalink. There might be some components that store their permalink in the articles record and if so just add the extra clause into the SQL statement above.
The Komento plugin for com_content.php has a more advanced article lookup where the various items are stored in different tables.
A permalink is the Non-SEF URL that Joomla uses to load pages and pretty much do most things.
To see a components permalink you need to turn off Search Engine Friendly URLs and Use URL Rewriting in Joomla's config and then browse to an article or category page for that component.
Menu Item Type Examples: RSFeedback - Add Category https://quantumwarp.com/index.php?option=com_rsfeedback&view=category&layout=edit&Itemid=1027 RSFeedback - Add Feedback https://quantumwarp.com/index.php?option=com_rsfeedback&view=feedback&layout=edit&Itemid=1027 RSFeedback Feedbacks View (List all Feedbacks) https://quantumwarp.com/index.php?option=com_rsfeedback&view=feedbacks&Itemid=1027 RSFeedback - List Categories https://quantumwarp.com/index.php?option=com_rsfeedback&view=categories&Itemid=1027 RSFeedback - Single Feedback https://quantumwarp.com/index.php?option=com_rsfeedback&view=feedback&id=2&Itemid=1027 Browsing Examples: RSFeedback Single Category https://quantumwarp.com/index.php?option=com_rsfeedback&view=feedbacks&cat_id=1&Itemid=1027
The Itemid=1027 - This is the menu item that my RSFeedback is displayed under. The other varibles are all straight forward.
Now we know what we need to build for Komento to understand what page to dispaly the comments on lets get on with it. there are several different methods that I have come across for build the permalink and I will list them below.
This is bar far the easiest and is the one that is in QW_com_sample
// Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink_field = $this->prepareLink( $this->_item->permalink_field );
As you can see it is literally is the permalink written out with the articles ID dynamically added at the end and then processed through a Komento function. The RSFeedback version lools like:
// Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink = $this->prepareLink( $this->_item->permalink );
As you can see they are exactly the same. RSFeedback uses simple permalink for its feedbacks. You will see we did not have to add the menu's Itemid as the compoenent already takes care of this. Not all compoenents will have a simple permalink like RSFeedback.
This example is taken from the JDownloads Komento integration plugin that ships in the Komento package and is in the folder /komento_plugins/ .
This function's output will override $_map->permalink but as you can see it allows for a more complex permalink to be built from various data sources much the same as JDownloads will build the links itself.
JDownloads Plugin Version
public function getContentPermalink() { $link = 'index.php?option=' . $this->component . ''; $pieces = array( 'option=' . $this->component, 'Itemid=' . $this->getItemId(), 'view=download', 'catid=' . $this->getCategoryId(), 'id=' . $this->getContentId() ); $link = $this->prepareLink( 'index.php?' . implode( '&', $pieces ) ); return $link; }
Example permalink:
JDownloads Single Download permalink https://quantumwarp.com/index.php?option=com_jdownloads&view=download&id=41:stackideas&catid=19&Itemid=859
I have not used this method so you will need to experiment, but it should not be that difficult now that you know what this 'Extended Function' does
So the function for RSFeedback is:
// This method should load the article's main properties based on article ID public function load( $cid ) { static $instances = array(); if( !isset( $instances[$cid] ) ) { // populate $this->_item with: // id_field // title_field // hits_field // created_by_field // catid_field // permalink_field // Create a Database Object $db = KT::getDBO(); // Create SQL query to load a single article $query = 'SELECT `id`, `title`, `hits`, `user_id`, `cat_id` FROM `#__rsfeedback_feedbacks` WHERE `id` = ' . $db->quote( $cid ); $db->setQuery( $query ); // Run the single article query and if there are no objects to load call the onLoadArticleError event if( !$this->_item = $db->loadObject() ) { return $this->onLoadArticleError( $cid ); } // Generate the permalink for this article $this->_item->permalink_field = 'index.php?options=com_sample&view=article&id=' . $this->_item->id_field; // Call the prepareLink function and leave the rest to us // Unless you have custom SEF methods, then use "getContentPermalink" function to overwrite $this->_item->permalink = $this->prepareLink( $this->_item->permalink ); $instances[$cid] = $this->_item; } $this->_item = $instances[$cid]; return $this; }
Again I have not used this method but I noticed that it used a router file (route.php). This example uses the com_content.php (Joomla Content) integration plugin that ships with Komento and is in the folder /komento_plugins/ .
Firstly the router file route.php for the component (com_content is actually a component in Joomla that is responsible for your articles)
public function __construct($component) { // Add com_content's router file $file = JPATH_ROOT . '/components/com_content/helpers/route.php'; $this->addFile($file); parent::__construct($component); }
It has a much more advanced getContentPermalink() function that utilises the route.php (router file for the com_content component)
public function getContentPermalink() { $slug = $this->_item->alias ? ($this->_item->id.':'.$this->_item->alias) : $this->_item->id; $catslug = $this->_item->category_alias ? ($this->_item->catid.':'.$this->_item->category_alias) : $this->_item->catid; $parent_slug = $this->_item->category_alias ? ($this->_item->parent_id.':'.$this->_item->parent_alias) : $this->_item->parent_id; $link = ContentHelperRoute::getArticleRoute($slug, $catslug); $link = $this->prepareLink($link); return $link; }
This function will load all article IDs for the specified categories. I am not 100% this is used in RSFeedback but anyway.
We need to update the SQL so it loads article IDs for the specified catgories.
QW Sample Version
// This method should load all the article IDs filtered by category IDs public function getContentIds( $categories = '' ) { // Create a Database Object $db = KT::getDBO(); // Make sure the query is empty $query = ''; // If no categories are supplied then load all article IDs if( empty( $categories ) ) { $query = 'SELECT `id_field` FROM `#__ARTICLE_TABLE` ORDER BY `id_field`'; } // If categories are supplied then load all article IDs for articles belonging to those categories else { if( is_array( $categories ) ) { $categories = implode( ',', $categories ); } $query = 'SELECT `id_field` FROM `#__ARTICLE_TABLE` WHERE `catid_field` IN (' . $categories . ') ORDER BY `id_field`'; } // Run the query and return the results as an array $db->setQuery( $query ); return $db->loadResultArray(); }
Dont forget to get the new field names by using phpMyAdmin to find out where the categories are stored and then work out the new SQL statement.
RSFeedback Version
// This method should load all the article IDs filtered by category IDs public function getContentIds( $categories = '' ) { // Create a Database Object $db = KT::getDBO(); // Make sure the query is empty $query = ''; // If no categories are supplied then load all article IDs if( empty( $categories ) ) { $query = 'SELECT `id` FROM `#__rsfeedback_feedbacks` ORDER BY `id`'; } // If categories are supplied then load all article IDs for articles belonging to those categories else { if( is_array( $categories ) ) { $categories = implode( ',', $categories ); } $query = 'SELECT `id` FROM `#__rsfeedback_feedbacks` WHERE `cat_id` IN (' . $categories . ') ORDER BY `id`'; } // Run the query and return the results as an array $db->setQuery( $query ); return $db->loadResultArray(); }
This function is used to build the category tree in Komento Integration admin so you can select what categories you want Kommento to work on.
This is quite an important function and you need to know whether your component has a 'single level' of categories or 'nested' categories (most joomla components do). The sample function below is rigged up for nested categories and might require the help of an additional function such as setLevel( $pid, $level, $categories, &$result ) to determine level. See the integration plugin com_jdownloads.php on how it was done as I dont think this example in QW_com_sample.php is complete.
QW Sample Version
// This method should load all the category IDs of the component // Make sure you select 'Single Level' or 'Nested' categories public function getCategories() { // Create a Database Object $db = KT::getDBO(); // Single Level Categories //$query = 'SELECT `id`, `title` FROM `#__CATEGORY_TABLE`'; // Nested Categories $query = 'SELECT `id`, `title`, `level`, `parent_id` FROM `#__CATEGORY_TABLE`'; // Run query and return categories $db->setQuery( $query ); $categories = $db->loadObjectList(); // Populate category tree for Komento Admin Integration Tab for this plugin (optional) // This is used in Komento where you select which categories the plugin should be active on etc... foreach( $categories as &$row ) { // Single Level Categories //$row->level = 0; // Nested Categories $repeat = ( $row->level - 1 >= 0 ) ? $row->level - 1 : 0; // Build and Add the Category Tree entry $row->treename = str_repeat( '.   ', $repeat ) . ( $row->level - 1 > 0 ? '|_ ' : '' ) . $row->title; } return $categories; }
JDownloads Plugin (com_jdownloads.php)
public function getCategories() { $sql = KT::sql(); $sql->select( '#__jdownloads_categories' ) ->column( 'id', 'id' ) ->column( 'title', 'title' ) ->column( 'parent_id' ) // ->where( 'published', 1 ) ->order( 'ordering' ); $categories = $sql->loadObjectList(); $result = array(); $this->setLevel( 0, 0, $categories, $result ); return $result; } private function setLevel( $pid, $level, $categories, &$result ) { foreach( $categories as &$category ) { if( (int) $category->parent_id === (int) $pid ) { $category->level = $level; $category->treename = str_repeat( '.   ', $level ) . ( $level > 0 ? '|_ ' : '' ) . $category->title; $result[] = $category; $this->setLevel( $category->id, $level + 1, $categories, $result ); } } }
RSFeedback has a very simple category storage as it is only a single level and not nested. If categories are in a nested format look at the com_content.php plugin or any of the other pluign files for examples.
I have altered the sample code just by
$row->level = 0; - This is to always set a level because this does not exist in a single level category system and it keeps the category tree building routine the same. You could in theory remove it but would make the code a bit messy ans should RSFeedback ever be upgraded to nested categories it would make sthings a lot easier to figure out.
RSFeedback Version
// This method should load all the category IDs of the component // Make sure you select 'Single Level' or 'Nested' categories public function getCategories() { // Create a Database Object $db = KT::getDBO(); // Single Level Categories $query = 'SELECT `id`, `name` FROM `#__rsfeedback_categories`'; // Run query and return categories $db->setQuery( $query ); $categories = $db->loadObjectList(); // Populate category tree for Komento Admin Integration Tab for this plugin (optional) // This is used in Komento where you select which categories the plugin should be active on etc... foreach( $categories as &$row ) { // Single Level Categories $row->level = 0; // Build and Add the Category Tree entry $row->treename = str_repeat( '.   ', $repeat ) . ( $row->level - 1 > 0 ? '|_ ' : '' ) . $row->name; } return $categories; }
This method lets Komento know if this is the front page or category layout (Determine if is listing view)
This is very simple to understand. We need to define what pages are categories and what are articles. This particular function tells Komento what are category or listing style pages and basically dont show commenting system on these.
QW Sample Version
// This method lets Komento know if this is the front page or category layout (Determine if is listing view) public function isListingView() { $views = array('categories', 'category', 'feedbacks'); return in_array(JRequest::getCmd('view'), $views); }
All you need to do is find the permalinks (addressed earlier) for all of the pages you do not want to show comments on and add them into the views array. I would do this by creating a menu item for every type of option available for RSFeedback and make a list of the permalinks into 2 groups, those you want comments on (should just be single feedback page) and a list of those you so not want comments on.
Of these you do not want comments on look at the permalink and you will see a GET parameter view=xxxx where xxxx is the view type. Add all of these view types into the $views = array() statement. Does not matter if there are duplicates just add the view type in once.
RSFeedback Version
// This method lets Komento know if this is the front page or category layout (Determine if is listing view) public function isListingView() { $views = array('category', 'categories', 'feedbacks'); return in_array(JRequest::getCmd('view'), $views); }
This is the opposite of above. This method lets Komento know if this is the page that the comment form should be displayed on (Determine if is entry view).
Basically follow the procedure from above but reverse it selecting only the views that you want comments on. I dont know why you need to specify view types you want comments on and not on rather that just the pages you want them on but i am sure there is a reason. There usually is only type of page(view) you want the comments to be displayed on.
QW Sample Version
// This method lets Komento know if this is the page that the comment form should be displayed on (Determine if is entry view) public function isEntryView() { return JRequest::getCmd('view') == 'article'; }
RSFeedback Verison
// This method lets Komento know if this is the page that the comment form should be displayed on (Determine if is entry view) public function isEntryView() { return JRequest::getCmd('view') == 'feedback'; }
With RSFeedback there is only 1 page type you want to display comments on, the Single feedback page.
public function onExecute( &$article, $html, $view, $options = array() )
This function outputs the HMTL code upon execution. You can also use it to append code to an article object. Tehre is not much more to this fuction.
QW Sample Version
// This method is the main method that appends Komento on the article public function onExecute( &$article, $html, $view, $options = array() ) { // $html is the html content generated by komento (includes listing and form) // Select 1 of the following outputs // This appends the HTML to the article object //$article->text .= $html; //return; // Return the Komento HTML code return $html; }
I uses the straight HMTL output for RSFeedback because the code in RSFeedback is geared up to recieve the HTML rather being added to the article object.
RSFeedback Version
// This method is the main method that appends Komento on the article public function onExecute( &$article, $html, $view, $options = array() ) { // $html is the html content generated by komento (includes listing and form) // Return the Komento HTML code return $html; }
We have now been through the basics of the Komento plugin with an emphasis on RSFeedback but these instruction like those before can be easily applied to other components.
RSFeedback supports several commenting systems out of the box (Inbuilt Comment System / RSComments / JComments / Jom Comments) but not Komento. Komento will have to be added into RSFeedback by altering some of it's core code.
Make sure you have backups and do all of this work on a test website first before working on your live site.
Because RSFeedback already has these comment systems installed I extracted the files and then did a text search to search for 'rscomments' and this return the following 3 files.
Extracted Installation Package Location
Location when installed in Joomla
Purpose of files
NB: File 3 does not make anydifference but is include for completeness
We now have the location in the code where we need to make changes you should go and have a quick look at the files to see how the comment systems are integrated. With systems like this is it is failry easy to add another commenting system as there will be standard that adds each commenting system in the same way, there will just be a slight difference in the final call to the commenting system and the included files that are need to call the commenting system.
This file adds the commenting systems as selections in the RSFeedback Joomla admin.
{add picture here}
Add the bottom of each of the groups of code I have simply added an entry for Komento giving it the option number 5, this is important for the next changes as it is the reference number we will be using for the Konto commenting system in RSFeeedback.
The following line is added to the bottom of the first group and is used as a boolean check to see if Komento is installed. You could probably use bootstrap.php instead but I am not sure about this and is not that important. Using koment.php certainly makes it easier to follow
$komento = file_exists(JPATH_SITE.'/components/com_komento/komento.php');
The second group of code in the fuction builds up the array for the RSFeedback admin and returns the results. Commenting systems that are not present will appear greyed out. Add the following line:
$commentsystem[] = JHTML::_('select.option', '5', JText::_('Komento') , 'value' , 'text', !$komento);
This will give you the full file shown below:
/** * @package RSFeedback! * @copyright (C) 2010-2014 www.rsjoomla.com * @license GPL, http://www.gnu.org/copyleft/gpl.html */ defined('_JEXEC') or die('Restricted access'); jimport('joomla.form.formfield'); class JFormFieldCommentingSystems extends JFormField { protected $type = 'CommentingSystems'; public function getInput() { $jcomment = file_exists(JPATH_SITE.'/components/com_jcomments/jcomments.php'); $jomcomment = file_exists(JPATH_SITE.'/plugins/content/jom_comment_bot.php'); $rscomment = file_exists(JPATH_SITE.'/components/com_rscomments/rscomments.php'); $komento = file_exists(JPATH_SITE.'/components/com_komento/komento.php'); $commentsystem = array(); $commentsystem[] = JHTML::_('select.option', '0', JText::_( 'COM_RSFEEDBACK_COMMENTS_DISABLED' ) ); $commentsystem[] = JHTML::_('select.option', '1', JText::_( 'COM_RSFEEDBACK_DEFAULT_COMMENTS' ) ); $commentsystem[] = JHTML::_('select.option', '2', JText::_('RSComments!') , 'value' , 'text', !$rscomment); $commentsystem[] = JHTML::_('select.option', '3', JText::_('JComments') , 'value' , 'text' , !$jcomment); $commentsystem[] = JHTML::_('select.option', '4', JText::_('Jom Comments') , 'value' , 'text', !$jomcomment); $commentsystem[] = JHTML::_('select.option', '5', JText::_('Komento') , 'value' , 'text', !$komento); $html = JHTML::_('select.genericlist', $commentsystem, 'jform[feedback_commenting]', '', 'value', 'text', $this->value); return $html; } }
Find the public static function DisplayComments($id) and add the following code at the end of the switch list. You will note it is option 5.
This is the code that will return the HTML from Komento (which has the comments for that Feedback item and the new comment form etc..) which is then assigned to the $comments->listing object which is the generic container RSFeedback uses to hold the comments from the various systems and is then this object that gets rendered below the Feedback giving you the comments.
case '5' : require_once( JPATH_ROOT . '/components/com_komento/bootstrap.php' ); $comments->listing = KT::commentify('com_rsfeedback', $id); $comments->form = ''; break;
This is a very similiar arrangement to rsfeedback.php except it is not used anywhere and all the code is within public function getComments(). The code here is slightly different but would do the same job if it was used.
add the following lines at the end of the switch list in the function public function getComments().
//Komento integration case 5: if (file_exists(JPATH_ROOT . '/components/com_komento/bootstrap.php')) : require_once(JPATH_ROOT . '/components/com_komento/bootstrap.php'); $comments .= KT::commentify('com_rsfeedback', $this->data->IdFeedback, $options); endif; break;
Firstly all of these are referenced in:
{Joomla Route}/administrator/components/com_komento/includes/komento.php
What is this all about. KT:: is the new name for the Komento class and it is this that should be used going forwards when making plugins even thought the official instructions (at time of writing) tell you to use Komento:: which is confusing. However the following code at the bottom of komento.php allows the legacy use of these class names.
class KMT extends KT {} class Komento extends KT {}
So going forwards just use KT:: so for example
require_once(JPATH_ROOT . '/components/com_komento/bootstrap.php'); Komento::commentify('com_sample', $article, $options);
should be
require_once(JPATH_ROOT . '/components/com_komento/bootstrap.php'); KT::commentify('com_sample', $article, $options);
NB: You can call KT:commentify() because it is a static function. I will not go into this further but you might notice a couple of these in the code.
We need to get the comments count from comment and feed this information back to RSFeedback so it can be displayed.
The function that Komento uses is getCount($component, $cid), in the KT:: class, in the file administrator/components/com_komento/includes/komento.php approx line 1355
$commentsModel = KT::model('Comments'); // Create a Comments object $commentCount = $commentsModel->getCount($component, $cid); // Return the Comment count
The function RSFeedback uses to display the comment count is getFeedbackCommentsCount($id) in the file components/com_rsfeedback/helpers/rsfeedback.php approx line 189
Again with previous codes there is a switch choice and we just need to add the Komento code in as option 5.
case '5' : require_once( JPATH_ROOT . '/components/com_komento/bootstrap.php' ); // Intialize Komento $commentsModel = KT::model('Comments'); // Create a Comments object $comments = $commentsModel->getCount('com_rsfeedback', $id); // Return the Comment count break;
The last thing to do is translate the new Komento Integration Tab and the dropdown menu option used in various places for our plugin in Komento by adding these translation strings.
COM_KOMENTO_COM_RSFEEDBACK="RSFeedback" COM_KOMENTO_SETTINGS_TAB_COM_RSFEEDBACK_SETTINGS="RSFeedback"
Thre are 2 ways of adding the translation string into joomla
We have now completed all of the steps required to integrate Komento into RSFeedback and have covered most aspects of this process. So he is a quick recap of what to do.
I am unable to directly move the database from my skeleton template on my staging server to a new target server. This is due to my staging server being newer and using MariaDB than the target server and that the default table type when using MariaDB is 'Aria' which the target server does not support.
Results
All of these exported files fail to import on the target with the following SQL error when importing in phpMyAdmin
Error SQL query: CREATE TABLE `xxxx_imageshow_external_source_picasa` ( `external_source_id` int( 11 ) unsigned NOT NULL AUTO_INCREMENT , `external_source_profile_title` varchar( 255 ) DEFAULT NULL , `picasa_username` varchar( 255 ) DEFAULT '', `picasa_thumbnail_size` char( 30 ) DEFAULT '144', `picasa_image_size` char( 30 ) DEFAULT '1024', PRIMARY KEY ( `external_source_id` ) ) ENGINE = Aria DEFAULT CHARSET = utf8 PAGE_CHECKSUM =1; MySQL said: Documentation #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PAGE_CHECKSUM=1' at line 8
If you use an intermediate server such as xampp you can convert the 'Aria' tabels in to 'MyISAM' which will then allow for a successful import.
Instructions
The skeleton database is now migrated and there are no 'Aria' type tables as the have all been converted to 'MYISAM'. You should also note that when using xampp on Windows all table names are changed to lowercase.
If you export the database in phpMyAdmin rather than exporting the tables there will be a database create rule and some header code is missing.
Error SQL query: -- -- Database: `skellyimport` -- CREATE DATABASE IF NOT EXISTS `skellyimport` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; MySQL said: Documentation #1044 - Access denied for user 'futured1'@'localhost' to database 'skellyimport'
To fix this, edit the SQL file and remove the following before importing
-- -- Database: `skellyimport` -- CREATE DATABASE IF NOT EXISTS `skellyimport` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `skellyimport`; -- --------------------------------------------------------
Error SQL query: /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; MySQL said: Documentation #1231 - Variable 'character_set_client' can't be set to the value of 'NULL'
The missing headers causes this error when importing into the target server. I am not sure what they do. But the difference between the phpMyAdmin Database and Table exports seems to be:
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */;
I also did a quick google search and found the following articles on the subject saying the same thing that the missing headers is what causes this particular error
Links
When you run RSSOwl it just never starts and no process is spawned.
The cause and the fix are easy.
Cause = RSSOwl requires Javascript 32-bit installed
Fix = Install Javascript 32 bit
Links