Updating - follow along

Ask the community for help and support.
Post Reply
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Updating - follow along

Post by Kofod95 »

In case someone could learn anything from this or just find it interesting, I will share some progress, thoughts, ideas and maybe tips and tricks here.

Background:
Two and a half year ago, I began working on a PhoenixCart 1.0.4.0-shop to replace a much older and unsupported cart software for my boss. I knew very little about anything at the time, but had messed around with the old shop enough to know that something had to be done to make the site more secure, user-friendly, responsive and future-proof.
My boss had a lot of request for functionalities and I had a lot of ideas and wishes so I did my best to fulfill them all, often by making core-code changes.
Now it's time for that shop to be updated and I curse my past self for the core-code changes!

Considerations
I considered a few different approaches:
  • Step-by-step upgrading
With the upgrader utility made by Zipurman, this could be done, however, this would mean a lot of downtime, while going through all the steps to reach the goal: 1.0.8.16 (actually 1.0.9.0, but that will have to wait until its release). I tried it out on a mirror-site and decided against it because of the time needed.
  • "Chunk" updates
I made a package and instructions that would bring a site from 1.0.4.0 to 1.0.5.0 in one jump and one more that would bring it to 1.0.6.0. I tried this on another mirror-site and it worked out ok, but the process of making the update packages and make sure not to make any errors was a bit rough.
For both of these approaches, the plan would be to go through the entire process on a mirror-site on the host-server, take note of all the steps, and then redo them on the live-site. Alternatively I could replace the live-site with the mirrored one, when it was thoroughly tested - that would depend on what would be easiest when I got there.
Another downside to both of the above approaches was that it wouldn't clear out all the core-code changes I had made (and I want to get rid of them) and the database would contain legacy stuff from my own changes, that I would have to clean up afterwards.
Also, there are several things I would like to take a different approach to now, which is easier done by taking a third approach:
  • Fresh start
By downloading the latest version (1.0.8.16 at the point of writing) and making/testing the add-ons I need on that, I can do things in the order and speed I want. First, I would make sure all the functionality needed is available, and next I would style the store. In the end, this completely new site would have to be loaded with the data of the live-site and replace it. This approach means that all modules must be configured again, all images will have to be copied and the new database will have to be populated very quickly as the new site replaces the old.
I chose this last option, as I wanted a fresh start and the jump to me seemed too big for the other approaches.

Process
My first step was to make a spreadsheet with all current functionalities/add-ons and design-changes, and add notes to each of them: How could they be done? Would another approach make it better and maybe usable in more cases than the specific ones they serve now? Are there any of the already available add-ons that will do the job? etc.
Next I downloaded and installed PhoenixCart 1.0.8.16 to a Xampp-server on my laptop. The first thing I added to this was Zipurman's Phoenix Bundler as that will make the process of creating new add-ons a lot easier. On the final install, the free version will be used to install everything and keep track of what's added to the site.

Next step was to download the add-ons already made by others, that supports 1.0.8.16 and adding links to the rest in my spreadsheet, so I can watch out for updates to them while making the things that are not already there. The downloaded add-ons were loaded with the bundler to make the installer hold every single add-on on the new site in the end.

Next post will touch on the process of making one or some of the add-ons I need to make myself.

//Daniel
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
14Steve14
VIP Member
VIP Member
Posts: 549
Joined: Fri Oct 25, 2019 7:01 pm
Has thanked: 8 times
Been thanked: 49 times

Re: Updating - follow along

Post by 14Steve14 »

It will be interesting to see how this goes as I am about to do a similar thing with my site. I always find it easier to start with the latest version, find the addons that are needed and add them. Then its just a case of updating the database which I find really difficult, but taking my time I get it all done, one table at a time.
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

I should probably mention that I have limited time per week to work on this. 8 hours is the most I will get, while most weeks will only have 3 hours.

Beginning with the things that was previously done with core changes, there are a few extra things we need to add to products. Those could have been added with hooks originally, but as mentioned, I didn't know too much when I began.
Most of these are simple extra things like "products_supplier", "products_type" etc, so I took a look at how manufacturers are managed. This means copying:
  • Admin/manufacturers.php
  • admin/includes/boxes/catalog_manufacturers.php
  • admin/includes/actions/manufacturers/
  • admin/includes/languages/.../manufacturers.php
  • admin/includes/languages/.../modules/boxes/catalog_manufacturers.php
Rename them and delete the lines that handles things that will not be needed (in this case all but the name) and add other things, if relevant, as well as change every remaining instant of "mID", MANUFACTURER, MANUFACTURERS, manufacturer, manufacturers and the language to sID, SUPPLIER, SUPPLIERS, supplier, suppliers or whatever fits the new page.

A new table must be added to the database to hold this new info and while at it, er might as well add the field into the products table:

Code: Select all

CREATE TABLE `products_suppliers` ( `suppliers_id` INT(11) NOT NULL AUTO_INCREMENT , `suppliers_name` VARCHAR(255) NOT NULL , PRIMARY KEY (`suppliers_id`));
ALTER TABLE `products` ADD `products_supplier` INT(11) NULL;
Next we must have a way of adding these new things to each product, which can be done with hooks. I'll give an example in the next post.
All of these things are of course needed for something (reports, catalog-layout, shipping prices etc), so I'll quickly touch on how to get that working in later posts as well.

//Daniel
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

Hooks are places in core that are calling for extra code. If the call is answered somewhere, that code will run along the core code. To answer the core hook-calls, our custom code has to listen:

Code: Select all

  public function listen_updateProductAction() {
and

Code: Select all

  public function listen_insertProductAction() {
and many more.

Everything within those functions will run, when core executes the hook-call:

Code: Select all

    $admin_hooks->cat(Admin::camel_case($action) . 'Action');
Input-fields can be added to the product-edit multiple places:
  • Or in a new tab (This would require a few more things than just adding a form)
With that, we create a new php-file xxx/includes/hooks/admin/catalog/productsSupplier.php and write the code we need:

Code: Select all

class hook_admin_catalog_productsSupplier {

    function listen_updateProductAction() {
		$this->listen_productActionSave();
	}

    function listen_insertProductAction() {
		$this->listen_productActionSave();
	}

	function listen_injectDataForm() {
		$this->load_lang();

		$supplier_options = array_merge([['id' => '', 'text' => TEXT_NONE]],
        $GLOBALS['db']->fetch_all("SELECT products_suppliers_id AS id, products_suppliers_name AS text FROM products_suppliers ORDER BY products_suppliers_name"));		
?>
        <div class="form-group row" id="zSupplier">
          <label for="pSupplier" class="col-form-label col-sm-3 text-left text-sm-right"><?= TEXT_PRODUCTS_SUPPLIER ?></label>
          <div class="col-sm-9">
            <?= (new Select('supplier', $supplier_options, ['id' => 'pSupplier']))->set_selection($GLOBALS['product']->get('supplier') ?? '') ?>
          </div>
        </div>
<?php		
	}

    function listen_productActionSave() {
      global $products_id, $db;

      $sql_data_array = ['products_supplier' => (int)$_POST['supplier'] ?? 0];

      $db->perform('products', $sql_data_array, 'update', "products_id = '" . (int)$products_id . "'");
    }

    function load_lang() {
      require_once language::map_to_translation('hooks/admin/catalog/products_supplier.php');
    }
}
First:

Code: Select all

class hook_admin_catalog_productsSupplier {
The hook is a new class we create, that is named according to its placement, so if we were making a hook to add things to the manufacturers-page, the class would be: hook_admin_manufacturers_productsSupplier.

Next:

Code: Select all

    function listen_updateProductAction() {
		$this->listen_productActionSave();
	}

    function listen_insertProductAction() {
		$this->listen_productActionSave();
	}
As I don't need different things to happen if the product is new rather than an existing one that is edited, I just make them do the same, which I define in another function.
I could also have put the code I wanted to happen in one of the above and made the one call that, but legacy-reasons made me do it like this.

Third:

Code: Select all

	function listen_injectDataForm() {
		$this->load_lang();

		$supplier_options = array_merge([['id' => '', 'text' => TEXT_NONE]],
        $GLOBALS['db']->fetch_all("SELECT products_suppliers_id AS id, products_suppliers_name AS text FROM products_suppliers ORDER BY products_suppliers_name"));		
?>
        <div class="form-group row" id="zSupplier">
          <label for="pSupplier" class="col-form-label col-sm-3 text-left text-sm-right"><?= TEXT_PRODUCTS_SUPPLIER ?></label>
          <div class="col-sm-9">
            <?= (new Select('supplier', $supplier_options, ['id' => 'pSupplier']))->set_selection($GLOBALS['product']->get('supplier') ?? '') ?>
          </div>
        </div>
<?php		
	}
This part makes things show up when editing a product. I begin by loading the language-constants I have added to a new file: includes/languages/english/hooks/admin/catalog/products_supplier.php - This file should of course be translated and copied to all other languages. That is done by calling a function "load_lang()" which is defined later in the file.

Next I gather the data I need. As this example allows setting pre-defined values, I need to know which values are available. I want the selection to be a drop-down menu, so I make sure to gather the data in a way that makes it easy to populate one, and add the option of not assigning a supplier or un-assign a product.
This is copied from how manufacturers are handled in core

Code: Select all

		$supplier_options = array_merge([['id' => '', 'text' => TEXT_NONE]],
        $GLOBALS['db']->fetch_all("SELECT products_suppliers_id AS id, products_suppliers_name AS text FROM products_suppliers ORDER BY products_suppliers_name"));	
The last part once again mirrors manufacturers, but changes the names as appropriate and adding $GLOBALS[''] where needed.

The last part is the functions called earlier, where I make sure to clean the input to protect the database (as I use id's here, I can just cast to integer like this: (int) - core has examples of how to handle text-inputs), and use language::map_to_translation to fetch the language-file.

//Daniel
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

Things are moving forward and I've only got a few tasks left, which were previously done with core-changes.
Some of them, however, are rather interesting and not too easy, so it'll be interesting to see how it goes.

This image shows the spreadsheet I made before I even began working. The green items are now completed. The red dots to the right in some of the cells are comments, telling me of things to look out for, or to save ideas I got.
As you can see, I've focused on the things that previously was done with core changes. Many of the other items just needs updating, though I'll probably end up rewriting some of them completely, to include new features or just improve on how they work now.
tasks.png
When I began the work, I made a second spreadsheet holding some info of every add-on. It looks like this:
process.png
The cells are labeled: Add-on | Notes | Override | Link.
The notes are mostly there to elaborate on the title, but I sometimes use them for other stuff as well. The override-cell is there to help me when I need to update the add-ons. The link is mostly for reference, but will also help keep track of updates for the add-ons made by others.

I'll follow up with some more code-progress next time

//Daniel
You do not have the required permissions to view the files attached to this post.
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

These last two weeks have left no time to work on this, as I had to fill in for a co-worker in the physical store.
This means I've been eager to come back to this type of work and to update this thread with some progress that were made before that.

One of the things I've made, using the steps explained previously, is a way to assign a location to every product to make it easier to find the ordered items. We will use this to mark products as "Temporarily out of stock" as well, so I added a way to change the location from the infoBox, without going to edit the product itself. This is done via a hook:

Code: Select all

class hook_admin_catalog_productsLocation {
   function listen_infoBox($parameters) {
      global $product; 

//Load the locations and set the variable $location_output to be a dropdown
//This is not included here, to save some space

		$parameters['contents'][] = ['class' => 'bg-success text-white text-center', 'text' => $location_output];
      }
    }
As we have this specific use for it, I added a report of all products with their location listed as well as a few other things we use (the model as our suppliers sku, and a padded products_id as our own sku). For that, I simply took Products Purchased from core as a starting point, copied it, renamed it and altered it to list the way I wanted. Next I added a hook that allows filtering the list by suppliers, as this is something we do a lot now: When we place an order with one of our suppliers, we can quickly check through that list and copy their sku for convenient re-ordering of our out-of-stock products.

Next, I needed a printer-friendly list of all products ordered. I took the page from above and made some changes. When it did what I wanted, I thought that it may be good to make it future-proof and able to save on paper, so I added a checkbox to each product that changes a field I added to orders_products, to signify whether or not the item had been collected or not. This is done via AJAX, so the page doesn't reload, as the list potentially could be rather long, which would make reloads a bit annoying.
I have never worked with AJAX before, so it took some time to make it work (and a lot of hair pulling!), but I managed to make it do as I wanted. Something that would have saved me a lot of time, was if I had spent some more time looking at the browsers developer tools.. I have used that tool a few times before for trying out different css-changes, but it is capable of so much more. Not just the kind of fun I used to do when I first learned of the powers of pressing f12:
dev-tools-1.png
It can track requests, which is very handy for AJAX-stuff, and I was able to see the URL that it was trying to load, as well as the output of the file I had made to update the database:
dev-tools-2.png
Stupid as I am, I had not included application_top in that file, so my database connection using $GLOBALS['db'] was not getting anywhere and threw a lot of errors. When I got to see the output of the file, I quickly realised why it wasn't working..

All the work above is currently available in the VIP-Forum (save for the products_supplier hook).

Next post will be about showing some of the data to customers on the Product Info Page, but there are still a few Lego-pieces I need to place in admin that also relates to this. We will come back to them later.

//Daniel
You do not have the required permissions to view the files attached to this post.
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

We want to let our customers easily be able to learn about our manufacturers. The site does not use boxes, so we want a content-product-info-module to show our manufacturers with some info.
cm_pi_manu_1.png
The manufacturer name and image links to the manufacturers page. Underneath, the SEO-description shows, and if you press "Read More", it will show the regular description and rotate the +, so it looks like an x instead:
cm_pi_manu_2.png
Creating something like this is not too difficult. First I took a random module from includes/modules/content/product_info and copied that, it's template-file (includes/modules/content/product_info/templates) and language-file(s) (includes/languages/english/modules/content/product_info) and renamed the file itself to cm_pi_manufacturer.php (this goes for module as well as language-file) or tpl_cm_pi_manufacturer.php (for the template-file).
Next I opened all files, and beginning with the module itself I replaced the class name

Code: Select all

  class cm_pi_manufacturer extends abstract_executable_module {
The key-base

Code: Select all

   const CONFIG_KEY_BASE = 'MODULE_CONTENT_PI_MANUFACTURER_';
and the constants in the get_parameters function.
I removed everything in the execute function, save for

Code: Select all

        $tpl_data = [ 'group' => $this->group, 'file' => __FILE__ ];
        include 'includes/modules/content/cm_template.php';
In the template file I changed the first line to:

Code: Select all

<div class="col-sm-<?= (int)MODULE_CONTENT_PI_MANUFACTURER_CONTENT_WIDTH ?> cm-pi-manufacturer text-center">
and got rid of whatever was between that opening div and the closing one.
The language-file(s) had the constants renamed as well, and I edited the text to suit my needs.

Next, I opened up includes/modules/boxes/manufacturer_info.php and it's template file, to see how core does what I want. I copied the lines that gathered the data and added lines for the description and seo_description:

Code: Select all

      $products_manufacturer = $GLOBALS['product']->get('brand');
      if (!empty($products_manufacturer)) {
        $_brand = $products_manufacturer->getData('manufacturers_name');
        $_image = $products_manufacturer->getData('manufacturers_image');
        $_id = $products_manufacturer->getData('manufacturers_id');
		$_short_desc = $products_manufacturer->getData('manufacturers_seo_description');
		$_desc = $products_manufacturer->getData('manufacturers_description');
I added this to the execute function in my new file and from there it was mostly just about adding the variables as output in my new template-file. Again, I looked at how the box-module did, but kept in mind that I had more horisontal space available, so the css-classes were changed.
The "Read More"-button took some inspiration from the hamburger-menu and my new module seems complete.

UPDATE:
A similar approach and something similar has been added to product_listings. This means the core-modules cm_ip_title.php and cm_ip_category_manufacturer_description.php will be uninstalled and replaced with this module.
cm_ip_extra.png
It took me around 1.5 hours to make both of these modules and I'm neither very experienced or well trained (actually not trained at all). This means most people would be able to do the same without spending days on it, so if you have ideas, try them out on a test-install, either locally (I use Xampp for that) or in a password-protected online mirror of your actual site. Many things are possible!

//Daniel
You do not have the required permissions to view the files attached to this post.
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

The product info pages are very flexible in layout with content-modules and the PI-System to take advantage of. The layout I'm going for on this site will be with a full-width product-title at the top. Underneath three columns, where one contains the SKU and the image, the next the price and some other general product data (weight, dimensions and specifications) and the last the buy-button and description. The bottom of the page will hold reviews, the above module for manufacturer-info and lastly the "Also bought".
We may put some x-sell, accessories or other similar things in as well, but it's not there now, so it's not part of the updating process.

For this layout to look good, the PI-system is needed, and we need all the modules in the three center columns to be PI-modules.
Just to give a quick example, this is how I made one to show the weight:
Take includes/modules/pi/product_info/pi_model.php and related files (includes/languages/$language/modules/pi/product_info/pi_model.php and includes/modules/pi/product_info/templates/tpl_pi_model.php)
rename them and change all instances of "model / MODEL / Model" to "weight / WEIGHT / Weight".
Usually, I change the default sort order of my new modules as well, to make them unique from core on install - this is done in the module-file, in the function get_parameters, the constant ending with SORT_ORDER and then the numbers after 'value' => .
Install and configure, and now the weight is shown on product-info, assuming a weight is entered. That's pretty easy to do!

Of course, since it's the weight, it could be made to show what scale is used. This could be added in the language file, but we could also make the module take that into account. I did that, and added an option for the customer to alternate between imperial and metric scale, so everyone can see the weight in a way that makes sense to them. To achieve that, I added my own function to the module file that is able to calculate and round the output, so it shows g or kg (or pounds / ounces) depending on the amount. I also added a parameter that enables the administrator of the website to choose what scale the weight is inserted in (we insert the weight as gram, but others may do it in kilogram, pounds or ounces, and this allows them to use the same module as we do, without changing the code).
This module was actually made some time ago, where it also includes width, height and depth and is available here. However, I had my reasons to remake it this time.

//Daniel
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

Two small things this time:
1) Give some more info about the module from yesterday - I was a little tired when I wrote the post, so I didn't go into as much detail as I wanted to
2) Add a custom 404-page - this helps keep customers at your site, instead of showing them your hosts default 404-page.

So let's have a look at the module. I've created a new module for the description of the product and this is what my module-file looks like:

Code: Select all

  class pi_description extends abstract_module {

    const CONFIG_KEY_BASE = 'PI_DESCRIPTION_';

    public $group = 'pi_modules_c';
    public $content_width;

    public function __construct() {
      parent::__construct();

      $this->group = basename(dirname(__FILE__));

      $this->description .= '<div class="alert alert-warning">' . MODULE_CONTENT_BOOTSTRAP_ROW_DESCRIPTION . '</div>';
      $this->description .= '<div class="alert alert-info">' . cm_pi_modular::display_layout() . '</div>';

      if ( $this->enabled ) {
        $this->group = 'pi_modules_' . strtolower(PI_DESCRIPTION_GROUP);
        $this->content_width = (int)PI_DESCRIPTION_CONTENT_WIDTH;
      }
    }

    public function getOutput() {
      $products_description = $GLOBALS['product']->get('description');
      if (!Text::is_empty($products_description)) {
        $tpl_data = ['group' => $this->group, 'file' => __FILE__];
        include 'includes/modules/block_template.php';
      }
    }

    protected function get_parameters() {
      return [
        'PI_DESCRIPTION_STATUS' => [
          'title' => 'Enable Model',
          'value' => 'True',
          'desc' => 'Should this module be shown on the product info page?',
          'set_func' => "Config::select_one(['True', 'False'], ",
        ],
        'PI_DESCRIPTION_GROUP' => [
          'title' => 'Module Display',
          'value' => 'C',
          'desc' => 'Where should this module display on the product info page?',
          'set_func' => "Config::select_one(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], ",
        ],
        'PI_DESCRIPTION_CONTENT_WIDTH' => [
          'title' => 'Content Width',
          'value' => '12',
          'desc' => 'What width container should the content be shown in?',
          'set_func' => "Config::select_one(['12', '11', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1'], ",
        ],
        'PI_DESCRIPTION_SORT_ORDER' => [
          'title' => 'Sort Order',
          'value' => '320',
          'desc' => 'Sort order of display. Lowest is displayed first.',
        ],
      ];
    }

  }
The first part:

Code: Select all

  class pi_description extends abstract_module {

    const CONFIG_KEY_BASE = 'PI_DESCRIPTION_';
Here I've renamed "model" and "MODEL" to "description" and "DESCRIPTION".
The CONFIG_KEY_BASE is used to fetch the language constants, so you can see the title and description of the module, when you are configuring it from admin. It may be used for other things as well, but I don't know for sure. Thus, if the title and description does not show in admin, check that this value is the same as what comes before "TITLE" and "DESCRIPTION" in your language file.

The next couple of things, I haven't touched at all, but the function getOutput() is rather important. I haven't done much to it here as I'm just showing things as they are in the database, but sometimes, more is needed here.

Code: Select all

 $products_description = $GLOBALS['product']->get('description');
$GLOBALS['product']->get fetches data from the product class, which automatically fetches everything asked for from the products and the products_description tables for the product in the database (probably a few tables more, like products_images). The field in products_description is actually called 'products_description', but Phoenix knows to look for things with 'products_' in front of it, so 'description' is enough to get the data I want.
If I wanted to show the weight it would have been: $GLOBALS['product']->get('weight').
If I wanted to show the supplier it would have been: $GLOBALS['product']->get('supplier') and so on.
Obviously this will sometimes just be an id, as is the case with manufacturers or the suppliers, and customers wouldn't have much joy from an id, so in that case, extra code to fetch the data related to that id would be needed.

Code: Select all

     if (!Text::is_empty($products_description)) {
This line (and the closing '}' later on) checks if there is any data, and if there is, it will include the template-file and the output in the template file will be shown

My template file is vey basic:

Code: Select all

<div class="col-sm-<?= (int)PI_DESCRIPTION_CONTENT_WIDTH ?> pi-description">
  <h5> <?= PI_DESCRIPTION_PUBLIC_TITLE ?> </h5>
  <?= $products_description ?>
</div>
The output is placed in a div with the size defined as the content-width and with a css-class named pi-description, to allow direct control over this content through css.
Next, I added a title for the description, which is defined in the language file.
Lastly, I output the variable defined in the module file, so it actually shows the description and then I close the opening div.

To finish this explanation, I'll touch on the get_parameters() function, in the bottom of the module file:

Code: Select all

        'PI_DESCRIPTION_STATUS' => [
          'title' => 'Enable Description',
          'value' => 'True',
          'desc' => 'Should this module be shown on the product info page?',
          'set_func' => "Config::select_one(['True', 'False'], ",
        ],
        'PI_DESCRIPTION_GROUP' => [
          'title' => 'Module Display',
          'value' => 'C',
          'desc' => 'Where should this module display on the product info page?',
          'set_func' => "Config::select_one(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'], ",
        ],
        'PI_DESCRIPTION_CONTENT_WIDTH' => [
          'title' => 'Content Width',
          'value' => '12',
          'desc' => 'What width container should the content be shown in?',
          'set_func' => "Config::select_one(['12', '11', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1'], ",
        ],
        'PI_DESCRIPTION_SORT_ORDER' => [
          'title' => 'Sort Order',
          'value' => '320',
          'desc' => 'Sort order of display. Lowest is displayed first.',
        ],
These are what will be put in the database when the module is installed, and for a PI-module these four are the minimum:
  • Status to toggle the module on and off while preserving the settings.
  • Group to put the module into the wanted group.
  • Content Width to decide how much horizontal space of the group this module is allowed to take up.
  • Sort Order to make it show in the correct order.
Each of those items have a minimum of three keys that needs to be set:
  • title, so you know what configuration you are looking at
  • value, to give a default value this module will have when installed
  • desch, to give further clarifications on what this actually controls and does
Some (like Content Width, Group and Status) also have a set_func, which shows the following values as options to choose from with a radio-button. Other things like this exist, to show a dropdown instead or to allows multiple entries to be selected with check-boxes.

If you need more things to be configurable in the module, it's easy to add another item to the list fx:

Code: Select all

 'PI_DESCRIPTION_EXAMPLE' => [
          'title' => 'Example',
          'value' => '1337',
          'desc' => 'This example does nothing, but could be a max-length of the description, before it's hidden with a Read More-thingy',
        ],
The above would do nothing with the module as is, so some code using the constant PI_DESCRIPTION_EXAMPLE should be written to make it do whatever you want it to do.
You could also add your own set_func values and then write the code to act on whatever is chosen.

And now for something completely different:

A custom 404-page is a page that will show, if someone tries to visit a non-existing page on your site. I often use mydomain.dk/pirat, as that is a very unlikely page to exist ("pirate"). To make your own 404-page, add this to .htaccess:

Code: Select all

#Custom 404
ErrorDocument 404 /404.php
create a page called 404.php (you could also change 404.php in .htaccess to something else like "ooops.php" and then make a page with that name instead), and make it show whatever you want it to.
I decided to make mine controlled by the InfoPages-system, so I copied shipping.php, templates/default/includes/pages/shipping.php and includes/languages/$language/shipping.php and renamed them to 404.php. In the templates/default/includes/pages/404.php I changed line 14:

Code: Select all

    'p.slug' => '404',
and line 18:

Code: Select all

  $breadcrumb->add($page['pages_title'], $Linker->build('404.php'));
Next I used admin->Tools->InfoPages to create a new page with the slug '404', made it 'Not Published' and wrote some text. Now this is my 404-page:
404.png
//Daniel
You do not have the required permissions to view the files attached to this post.
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
User avatar
Kofod95
VIP Member
VIP Member
Posts: 605
Joined: Sat Feb 06, 2021 7:38 pm
Has thanked: 80 times
Been thanked: 141 times

Re: Updating - follow along

Post by Kofod95 »

I promised that we would get back to some things and we may still do, but for now, I will instead walk through the update itself.

I wanted to wait until 1.0.9.0, but as we are on Release Candidates now and I will soon be too busy to take an entire evening out, I decided to do the update now.
First, I installed a clean 1.0.8.20 on my PC.
Next, I configured it to match the wanted results on the live-store and made the css-changes etc.
I then installed the Zipur-bundler lite and loaded all my packages into the "updates" folder, installed them and configured the modules.
I tested all elements of that site that I could think of, and noted the bugs I found (some of them due to two or more of my add-ons not working together too well). I went to the test-store they were created on, corrected the errors and reloaded them onto the new 1.0.8.20-shop. When that was error-free, I took the data we add ourselves from the live database, and restructured when necessary, so that it would fit into the new shop. One example of this was products-specifications which were previuosly stored as text in a table, but I've split that into two separate tables on the new store; one that handles specific specifications (countries, shapes etc) and another that handles the more generic kind (dimensions, materials etc). The first kind is now stored as id's, making it possible to edit the value for all products at once, while the second one is stores as text still.
When that was done, I downloaded all the images that were used on the live-shop and loaded them into this local staging-site.

Now the real fun began!
I made a back-up of the database of the local staging-site, and installed a brand new 1.0.8.20 on a sub-domain on the server where the live-site is and password-protected it through .htaccess. Next, I uploaded the local staging-site, except for the two configure files. Those were moved to the includes/local folders, and the configure-files from the server were put in their place, though I changed the path to reflect the main domain.
I then went and loaded the database onto the sub-domain site from the back-up I made.
I tested everything once again before I proceeded.
Next, I waited for a time when not many visitors would be at the site, and I then transferred all orders and their data as well as all customers and their data to the new site (I had planned this ahead, so I could use one query for orders and another for customers that made sure all data were migrated as quickly as possible). Then, I removed the password to the sub-domain and made the main domain redirect to the subdomain. Now, our updated shop was live!
I made a new database-backup, downloaded the old site from the main domain and uploaded it to a new sub-domain with password protection (just to make sure to have a fall-back in case of something bad I've missed on the new site). Then, I deleted the old site from the main domain and uploaded the copy from my PC, except the two configure files in includes/local.
Then came the time to remove the redirect, and add the password protection to the sub-domain again.
1.0.8.20 is live for us, with a local site that changes and future updates will be made on and a online site that is password-protected, that enables us to check stuff in the environment before applying them to the live. This means that I can develop locally and test in the real environment before anything will be applied to the actual site.
When enough time has passed, that I'm confident I will not need the back-up of the old store online anymore, I will delete it, but it will stay for at least a day or two!

I don't know if this is a good way of doing things, but it worked fine for us!

//Daniel
I'm not smart, but sometimes even a blind chicken can find a corn.
Here are a lot of corns: Phoenix user guide
Post Reply