Post by richcon » Sat Jul 10, 2010 8:12 am

I want to create a module that would add a new table to the database. (The purpose would be to store information about product videos.) I know how to do the SQL coding. Does OpenCart allow modules to do this?

Looking through the source I see there's a controller folder for modules (catalog/controller/module) and a view folder (catalog/view/theme/default/template/module), but I can't find a model folder for modules.

Thanks,
Richard

New member

Posts

Joined
Thu Apr 15, 2010 7:58 am
Location - Los Angeles

Post by Xsecrets » Sat Jul 10, 2010 9:35 am

yes modules can do that. The model folder for modules doesn't exist, but you can create it. If you look at the tag cloud module you can see how I created and call a model file for a module.

OpenCart commercial mods and development http://spotonsolutions.net
Layered Navigation
Shipment Tracking
Vehicle Year/Make/Model Filter


Guru Member

Posts

Joined
Sun Oct 25, 2009 3:51 am
Location - FL US

Post by richcon » Sat Jul 10, 2010 10:20 am

Thanks!

Do you know where there's any good documentation online for developing for OpenCart? There's really no comments in the code and the documentation at opencart.com doesn't go into any detail beyond the barest basics (MVC, etc).

Rich

New member

Posts

Joined
Thu Apr 15, 2010 7:58 am
Location - Los Angeles

Post by richcon » Sat Jul 10, 2010 10:49 am

By the way, I looked through your code and saw how it works. I'm starting to figure out how module controllers function, have a dummy one put together right now. Lots of trial and error. :)

New member

Posts

Joined
Thu Apr 15, 2010 7:58 am
Location - Los Angeles

Post by Qphoria » Mon Jul 12, 2010 11:42 pm

From any controller, model, or tpl file you can use:

$this->db->query()

to send or receive anything from the database:

$results = $this->db->query("select * from table");
$this->db->query("insert into table set xxx = 'yyy');
etc

Image


User avatar
Administrator

Posts

Joined
Tue Jul 22, 2008 3:02 am

Post by richcon » Tue Jul 13, 2010 4:15 am

Thanks Qphoria! I saw Xsecrets using it in his module.

Do you know if there's any kind of "install" and "uninstall" action that's run on the module when it's installed, so I can set up the new database table? Or should I provide an "install" button in the admin panel view and do it there?

Thanks,
Rich

New member

Posts

Joined
Thu Apr 15, 2010 7:58 am
Location - Los Angeles

Post by Qphoria » Tue Jul 13, 2010 5:13 am

Up to 1.4.8b, the only way to install extra stuff is to put an additional function that gets triggered when the index() function is called that will check the db and make the changes if necessary each time the edit page is loaded.

In 1.4.9, I've added an automatic call to check if the module has its own install/uninstall function. If so, then it will be called during the install / uninstall steps instead of having to put it at the edit step

Image


User avatar
Administrator

Posts

Joined
Tue Jul 22, 2008 3:02 am

Post by richcon » Tue Jul 13, 2010 6:28 am

Qphoria wrote:In 1.4.9, I've added an automatic call to check if the module has its own install/uninstall function. If so, then it will be called during the install / uninstall steps instead of having to put it at the edit step
Awesome, I'll be using it when it's available. For now I thought of putting the check in the 'index' action, but instead I think I'll create an "install" action usable from the Admin panel (an "Install" button called from the module's control panel page).

One of the implications of your "uninstall" function is that dropping the extra table from the database means permanently deleting everything the module created. In my plugin all product videos would be wiped clean from the database. We might not want to do that without some sort of big flashing warning first. Or maybe you could prompt the user to ask if they want to run the uninstall step (deleting all its data), or if they just want to temporarily disable it (not deleting the data)?

New member

Posts

Joined
Thu Apr 15, 2010 7:58 am
Location - Los Angeles

Post by qahar » Thu Jul 15, 2010 11:15 am

Qphoria wrote:In 1.4.9, I've added an automatic call to check if the module has its own install/uninstall function. If so, then it will be called during the install / uninstall steps instead of having to put it at the edit step
cool.. very awesome Q..
is there any information about next 1.49 feature?

User avatar
Expert Member

Posts

Joined
Tue Jun 29, 2010 10:24 pm
Location - Indonesia

Post by Chris_UK » Sun Aug 11, 2019 6:25 pm

I started developing my own modules in 2.x, with that I am not sure which version the install/uninstall functions were available by default.

What i want to address here is the concern that the uninstall function would automatically erase data. While removing a module does remove its data from the settings table, if the uninstall function is available and triggered it does only what you tell it to.

install: You create and populate tables even alter and populate existing tables.

Uninstall: You can optionally revert all of your changes, using install does not mean you have to use uninstall. That said, if a module is to be uninstalled then the data should really be removed, a user may not want to use the module any longer and so wants to remove it from their store entirely. You should accommodate this.

If you use neither of these options (and don't handle the install elsewhere) then all of the saved data about the module go into the settings database:

This line in the controllers (again I know only of oc[2.0 -> 2.1]) is what saves your settings when you click save in a module using the default setup.

Code: Select all

$this->model_setting_setting->editSetting('your_module', $this->request->post);
To break that line down:
'your_module' goes into the code column

The post data is a key => value pair for each form element and is entered into the key and value columns of the database respectively with the key being the name of the form input it came from and value being the user input it contained.

When getting information back for the form you load it back into the $data array.
Personally I do the following because its easier than writing line after line for each form element, its a bug in my bonnet when building modules, masses of repetition.

Code: Select all

         // I add this into the constructor if i've used one otherwise its in which ever method im populating form data from
        $this->settings = $this->model_setting_setting->getSetting('module_name');
         
         // now i Lazy Load the settings for the form in the index method (or whichever method is using it).
        foreach ($this->settings as $key => $val) {
            $data[$key] = $val;
        }
I do similar for form error checking and language loading too but as its getting in depth and a little off topic I am going to leave it here. If anybody would like to know more feel free to drop me a message on the forum, I will be happy to share an example of how i do these too.

New member

Posts

Joined
Wed Jan 20, 2016 4:39 am

Post by OSWorX » Sun Aug 11, 2019 8:10 pm

Chris_UK wrote:
Sun Aug 11, 2019 6:25 pm

Code: Select all

$this->settings = $this->model_setting_setting->getSetting('module_name');

Code: Select all

foreach ($this->settings as $key => $val) {
            $data[$key] = $val;
        }
The drawback of doing so, is that when you load the new extension the first time, no default values are given.
Nor the fields are filled with default values.

Also, if you add later additional variables and values, the extension will produce errors.

What I do always is like that:

Code: Select all

$vars = array(
'status' => 0,
'sort_order' => 0,
'var1' => 'val1',
'var1 => 'val2',
'var3' => array(),
'var4 => array( 'subvar1' => 1, 'subvar2' => 'whateveryouwant' ),
and so on ..
Values can be integers, floats, texts . .any king of type.

And after that something like:

Code: Select all

foreach( $vars as $k => $v ) {
	if( !is_null( $this->config->get( $this->_name . '_' . $k ) ) ) {
		if( is_array( $v ) ) {
			$data['cfg_' . $k] = $this->config->get( $this->_name . '_' . $k ) + $v;
		}else{
			$data['cfg_' . $k] = $this->config->get( $this->_name . '_' . $k );
		}
	}else{
		$data['cfg_' . $k] = $v;
	}
}
This kind of code is highly portable (can be used with any type of extension).
And I do not have to take care on the extensions name (which es defined prior > see _name above), because inside the template (no matter which type), the values wil have all a prefix cfg_ which will be replaced when storing the settings.
Also adding new variables at any time will produce a correct result.

Full Stack Web Developer :: Dedicated OpenCart Development & Support DACH Region
Contact for Custom Work / Fast Support.


User avatar
Guru Member

Posts

Joined
Mon Jan 11, 2010 10:52 pm
Location - Austria

Post by Chris_UK » Sun Aug 11, 2019 10:06 pm

OSWorX wrote:
Sun Aug 11, 2019 8:10 pm
This kind of code is highly portable (can be used with any type of extension).
And I do not have to take care on the extensions name (which es defined prior > see _name above), because inside the template (no matter which type), the values wil have all a prefix cfg_ which will be replaced when storing the settings.
Also adding new variables at any time will produce a correct result.
Thank you for your response, I don't make many modules and when i do it tends to be for a specific purpose for my own website so its not something I have run into yet, however I can see your reasoning and portable code is always a bonus in my book.

I am going to look into some of my modules and update them. I may never release them into the wild but if I do I don't want silly bugs causing issues when they can be prevented.

New member

Posts

Joined
Wed Jan 20, 2016 4:39 am

Post by OSWorX » Sun Aug 11, 2019 11:23 pm

After several years of developing extensions (modules, plugins, etc.) for OC, I know that some time the extension has to be updated.
Updated in a way that I have to add one ore more variables.
Doing like I described, such a task is just a matter of 1,2 minutes.

Also, I do not assign each and every language variable as the core does.
Get them all with 1 line - and if (in rare cases) a language variable has to overriden or assigned specially, this will be also jut 1 line (more).

Additonally - what a some of the (better) OC devs are doing (some of them doing it , but not in a good way!), is to 'summarize' common tasks and functions.
If you have once 40, 50 or more extensions, you will have all those in a serperate, own library - from where they can be called at simply any time (without writing again and again and again the same stuff).

There are maybe a few more tips, maybe one of the others here have some?

Full Stack Web Developer :: Dedicated OpenCart Development & Support DACH Region
Contact for Custom Work / Fast Support.


User avatar
Guru Member

Posts

Joined
Mon Jan 11, 2010 10:52 pm
Location - Austria

Post by Chris_UK » Tue Aug 27, 2019 4:14 am

OSWorX wrote:
Sun Aug 11, 2019 8:10 pm
What I do always is like that:

Code: Select all

$vars = array(
'status' => 0,
'sort_order' => 0,
'var1' => 'val1',
'var1 => 'val2',
'var3' => array(),
'var4 => array( 'subvar1' => 1, 'subvar2' => 'whateveryouwant' ),
and so on ..

I just knocked up this little method to extract the field names directly from the template, in doing so no manually edited arrays.

Code: Select all

function fieldExtractor($input) {
	$dom	 = new DOMDocument;
	$dom->preserveWhiteSpace = false;
		
	/** DOMDocument::loadHTML warnings suppressed with @
	* A html 4 DTD is used by loadHTML as a fallback when no DTD is present in the input.
	* 
	* OC uses html5 which has no DTD and so produces lots of notices.
	*/
	@$dom->loadHTML($input);

	$x_path = new DOMXPath($dom);
		
	// The form fields from the template in the input.
	$fields = $x_path->query('//input|//textarea|//select');

	if (is_object($fields)) {
		foreach ($fields as $field) {
			$form_fields[] = $field->getAttribute('name');
		}
	}
	return $form_fields;
}
To make use of it in the extension or module.

Code: Select all

	$this->load->model('setting/setting');
	$this->settings = $this->model_setting_setting->getSetting('module_code');

	// parse the same template as will be called used for the response output.
	$form_fields =  fieldExtractor($this->load->view('directory/template.tpl'));

	foreach ($form_fields as $iteration => $field) {
		if (array_key_exists($field, $this->settings)) {
			$data[$field] = $this->settings[$field];
		} else {
			$data[$field] = '';
		}
	}
With this method in place, I now never need to worry about making changes to extensions settings. If I want to add or remove an option I only need to edit the template file and the array is automatically adjusted.

As these are just fallback values I see no reason currently to type cast or even give a real default value but if there is a case for it, the query and foreach within the function could be split to handle it.

Hopefully its quite portable too.

New member

Posts

Joined
Wed Jan 20, 2016 4:39 am

Post by OSWorX » Tue Aug 27, 2019 5:56 pm

Not a bad Idea ..

Full Stack Web Developer :: Dedicated OpenCart Development & Support DACH Region
Contact for Custom Work / Fast Support.


User avatar
Guru Member

Posts

Joined
Mon Jan 11, 2010 10:52 pm
Location - Austria

Post by Chris_UK » Tue Aug 27, 2019 6:21 pm

I have just revised the code to reduce the code in the controller and renamed it to clarify its function. If anybody wants to use it, that's totally fine with me.

Also by combining it all into one function I was able to drop the second foreach, its pointless doing two when the whole thing can be wrapped into a single foreach.

Code: Select all

	/**
	 * @param string $input Path to template file
	 * @param array $settings Extension settings from the database
	 * @return array our template vars
	 */
	function formVarCreator($input, $settings) {
		$dom = new DOMDocument;

		$dom->preserveWhiteSpace = false;
                
                // Used instead of the load->view method of OpenCart.
                $content = file_get_contents($input);
                
		/**
		 * loadHTML relies on DTD to determine if a html document is
		 *  formed correctly and falls back on 4.0 transitional when no
		 * DTD is found in the document.
		 *
		 * You may make use of "internal errors" if you prefer to handle
		 * errors in some way.
		 *
		 * In this case, the template isn't a fully formed HTML document
		 * being  just one part of the full view and will always result in
		 * many notices from loadHTML.
		 *
		 *  Here errors are suppress with @.
		 */
		@$dom->loadHTML($content);

		$x_path = new DOMXPath($dom);

		$paths = $x_path->query('//input|//select|//textarea');

		if (is_object($paths)) {
			foreach ($paths as $node) {

				$field = $node->getAttribute('name');

				if (array_key_exists($field, $settings)) {
					$form_fields[$field] = $settings[$field];
				} else {
					$form_fields[$field] = '';
				}
			}
		}
		return $form_fields;
	}

 // place this before the view loader line.
$data = array_merge($data, formVarCreator(DIR_TEMPLATE . 'shipping/parcel2go.tpl', $this->settings));
UPDATE: Although the notices are suppressed at loadHTML, the previous version used the OpenCart's view loader to supply the html we needed. This was an error on my part, I had not considered that the view loader was the point in the application where the notices were being sent in the first place.

To remedy this issue we now make use of file_get_contents instead of using output from the view loader, this change means the first parameter of the function will now be the path to the template file. We still need the error suppression within the function as the template is not a fully formed html document.

New member

Posts

Joined
Wed Jan 20, 2016 4:39 am
Who is online

Users browsing this forum: No registered users and 42 guests