Jump to content



Photo
- - - - -

How to make a 2.4 App


  • Please log in to reply
33 replies to this topic

#21   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 21 March 2017 - 10:32

@clustersolutions

 

No, but I can :)

 

I'll put it up tomorrow


Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#22   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 22 March 2017 - 22:01

I will put this up on Github when I've changed the Vendor/App name throughout the app.

 

For now, let's get the content module working.

 

First of all we need to let osC know about the parameters, so that when we choose Configure (to configure modules) from the App menu they are all there for us to change.

As mentioned previously, all parameters go in the folder OSC/Apps/Test/Test/Module/Admin/Config/APPTEST/Params, and the corresponding language files go in OSC/Apps/Test/Test/languages/english/modules/APPTEST/Params

Some important points.

  • Each parameter has it's own parameter file.
     
  • The filename for the parameter is lower case and matches exactly the class name in the file. Extension is .php
     
  • The language define filename also matches the class name exactly, but with a .txt extension.
     
  • Upon module installation, the class/filename is made uppercase, added to the app configuration key prefix defined in OSC/Apps/Test/Test/Module/Admin/Config/ConfigAbstract.php (in this case $cut_length = strlen('OSCOM_APP_TEST_' . $this->code . '_'); which translates for our module app into OSCOM_APP_TEST_APPTEST_) and added to the database.

So, for example, the status parameter is defined in OSC/Apps/Test/Test/Module/Admin/Config/APPTEST/Params/status.php with a class name of status, and gets inserted into the database as OSCOM_APP_TEST_APPTEST_STATUS

Another example, the content_width parameter is defined in OSC/Apps/Test/Test/Module/Admin/Config/APPTEST/Params/content_width.php with a class name of content_width, and gets inserted into the database as OSCOM_APP_TEST_APPTEST_CONTENT_WIDTH

Rather than post every single parameter for this module, I'll zip them up and make them available for download (see below).

However, if you make an app, here are some examples of parameter files:

Status for a module (which is 1 or -1 so that osC can see if it's enabled or not)

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\Test\Test\Module\Admin\Config\APPTEST\Params;

use OSC\OM\HTML;

class status extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract
{
    public $default = '1';
    public $sort_order = 1;

    protected function init()
    {
        $this->title = $this->app->getDef('cfg_apptest_status_title');
        $this->description = $this->app->getDef('cfg_apptest_status_desc');
    }

    public function getInputField()
    {
        $value = $this->getInputValue();

        $input = '<div class="btn-group" data-toggle="buttons">' .
                 '  <label class="btn btn-info' . ($value == '1' ? ' active' : '') . '">' . HTML::radioField($this->key, '1', ($value == '1')) . $this->app->getDef('cfg_apptest_status_true') . '</label>' .
                 '  <label class="btn btn-info' . ($value == '-1' ? ' active' : '') . '">' . HTML::radioField($this->key, '-1', ($value == '-1')) . $this->app->getDef('cfg_apptest_status_disabled') . '</label>' .
                 '</div>';

        return $input;
    }
}

A simple True/False parameter:
 

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\Test\Test\Module\Admin\Config\APPTEST\Params;

use OSC\OM\HTML;

class show_name extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract
{
    public $default = 'True';
    public $sort_order = 30;

    protected function init()
    {
        $this->title = $this->app->getDef('cfg_apptest_show_name_title');
        $this->description = $this->app->getDef('cfg_apptest_show_name_desc');
    }

    public function getInputField()
    {
        $value = $this->getInputValue();

        $input = '<div class="btn-group" data-toggle="buttons">' .
                 '  <label class="btn btn-info' . ($value == 'True' ? ' active' : '') . '">' . HTML::radioField($this->key, 'True', ($value == 'True')) . $this->app->getDef('cfg_apptest_show_name_true') . '</label>' .
                 '  <label class="btn btn-info' . ($value == 'False' ? ' active' : '') . '">' . HTML::radioField($this->key, 'False', ($value == 'False')) . $this->app->getDef('cfg_apptest_show_name_disabled') . '</label>' .
                 '</div>';

        return $input;
    }
}

A parameter with text input:
 

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\Test\Test\Module\Admin\Config\APPTEST\Params;

class name_length extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract
{
    public $default = '36';
    public $sort_order = 40;
    

    protected function init()
    {
        $this->title = $this->app->getDef('cfg_apptest_name_length_title');
        $this->description = $this->app->getDef('cfg_apptest_name_length_desc');
    }
}

A parameter with a select (i.e. for content width)
 

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\Test\Test\Module\Admin\Config\APPTEST\Params;

use OSC\OM\HTML;

class content_width extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract
{
    public $default = '12';
    public $sort_order = 4;

    protected function init()
    {
        $this->title = $this->app->getDef('cfg_apptest_content_width_title');
        $this->description = $this->app->getDef('cfg_apptest_content_width_desc');
    }

    public function getInputField()
    {
        
        for ($i=1; $i<13; $i++) {
        $width_array[] = array('id' => $i, 'text' => $i);
    }


        $input = HTML::selectField($this->key, $width_array, $this->getInputValue());

        return $input;
    }
}

Here is an example of a language definition file:
 

cfg_apptest_status_title = Status
cfg_apptest_status_desc = Set this to Enabled to use the Product Info Page module.

cfg_apptest_status_true = Enabled
cfg_apptest_status_disabled = Disabled

Once uploaded, osC will pick them up the language definitions automatically so no need to add them to the parameters file.

There is also a setting you can add which will "lock" the parameter. Say, for instance you want to make the sort order fixed, add the line
 

public $app_configured = false;

to the file, like this
 

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\Test\Test\Module\Admin\Config\APPTEST\Params;

class sort_order extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract
{
    public $default = '100';
    public $sort_order = 200;
    public $app_configured = false;

    protected function init()
    {
        $this->title = $this->app->getDef('cfg_apptest_sort_order_title');
        $this->description = $this->app->getDef('cfg_apptest_sort_order_desc');
    }
}

If you want to configure any parameters through the traditional module admin (for example Admin -> Legacy -> Modules -> Content) then add the line
 

public $app_configured = false;

to the parameter as above, then add the paramter to the module file (for example OSC/Apps/Test/Test/Module/Content/APPTEST.php) like this
 

    function keys() {
        return array('OSCOM_APP_TEST_APPTEST_SORT_ORDER');
    }

Note: I haven't done that with any of the parameters in this module.

Here are the parameters, which go in OSC/Apps/Test/Test/Module/Admin/Config/APPTEST/Params

Attached File  Params.zip   9.14KB   0 downloads

Here are the language definitions for the parameters, which go in OSC/Apps/Test/Test/languages/english/modules/APPTEST/Params

Attached File  LangParams.zip   4.1KB   0 downloads

Finally, the upgraded content module. As you recall, it was filled with dummy static content but now we want to display generated content.

Open OSC/Apps/Test/Test/Module/Content/APPTEST.php and replace the contents with this
 

<?php
/**
  * Test App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 osCommerce; https://www.oscommerce.com
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

  namespace OSC\Apps\Test\Test\Module\Content;

  use OSC\OM\HTML;
  use OSC\OM\OSCOM;
  use OSC\OM\Registry;
  use OSC\Apps\Test\Test\Test as TestApp;

  class APPTEST implements \OSC\OM\Modules\ContentInterface {
    public $code, $group, $title, $description, $sort_order, $enabled, $app;

    function __construct() {
      if (!Registry::exists('Test')) {
        Registry::set('Test', new TestApp());
      }

      $this->app = Registry::get('Test');
      $this->app->loadDefinitions('modules/APPTEST/APPTEST');

      $this->code = 'APPTEST';
      $this->group = 'product_info';

      $this->title = $this->app->getDef('module_apptest_title');
      $this->description = '<div align="center">' . HTML::button($this->app->getDef('module_apptest_legacy_admin_app_button'), null, $this->app->link('Configure&module=APPTEST'), null, 'btn-primary') . '</div>';
       
      
          if ( OSCOM_APP_TEST_APPTEST_STATUS < '1' ) {

            $this->enabled = false;
          } else {
            $this->enabled = true;
          }
    }
    
    function execute() {
      global $oscTemplate, $languages_id, $currencies, $currency, $PHP_SELF, $product_info;
      
      $content_width = OSCOM_APP_TEST_APPTEST_CONTENT_WIDTH;
      $product_width = OSCOM_APP_TEST_APPTEST_DISPLAY_EACH;

      $optional_rel_prods_content = NULL;
      
      $orderBy = 'order by ';
      $orderBy .= (OSCOM_APP_TEST_APPTEST_RANDOMIZE == 'True')?'rand()':'pop_order_id, pop_id';
      $orderBy .= (OSCOM_APP_TEST_APPTEST_MAX_DISP > 0)?' limit ' . OSCOM_APP_TEST_APPTEST_MAX_DISP:'';

      $optional_rel_prods = "SELECT ";
      $specials_query = '';

      if (OSCOM_APP_TEST_APPTEST_SHOW_QUANTITY == 'True')
          $optional_rel_prods .= "products_quantity, ";

      if (OSCOM_APP_TEST_APPTEST_SHOW_MODEL == 'True')
          $optional_rel_prods .= "products_model, ";

      if (OSCOM_APP_TEST_APPTEST_SHOW_THUMBS == 'True')
          $optional_rel_prods .= "products_image, ";
                                       
      if (OSCOM_APP_TEST_APPTEST_SHOW_PRICE == 'True') {
          $optional_rel_prods .= "products_tax_class_id, products_price, IF(s.status, s.specials_new_products_price, NULL) as specials_products_price, ";
          $specials_query = " left join :table_specials s on s.products_id = pb.products_id ";
      }

      if (OSCOM_APP_TEST_APPTEST_SHOW_NAME == 'True')
          $optional_rel_prods .= "products_name, ";        

      if (OSCOM_APP_TEST_APPTEST_SHOW_DESCRIPTION == 'True')
        $optional_rel_prods .= "substring_index(pa.products_description, ' ', " . OSCOM_APP_TEST_APPTEST_DESCRIPTION_LENGTH . ") as products_description, ";
        
      $optional_rel_prods .= "pop_products_id_slave ";
      
      $optional_rel_prods .= "from
                                                     :table_products_related_products,
                                                     :table_products pb
                              left join :table_products_description pa on pa.products_id = pb.products_id
                                                     " . $specials_query . "
                                                     where pop_products_id_slave = pa.products_id
                                                     and pa.products_id = pb.products_id
                                                     and language_id = :languages_id
                                                     and pop_products_id_master = :products_id
                                                     and products_status='1' " . $orderBy;
      
            $Qrelated = $this->app->db->prepare($optional_rel_prods);
            $Qrelated->bindInt(':languages_id', $this->app->lang->getId()); //(int)$languages_id
            $Qrelated->bindInt(':products_id', $_GET['products_id']); //(int)$_GET['products_id']
            $Qrelated->execute();

                if ($Qrelated->rowCount() > 0) {
 
                $optional_rel_prods_content .= '<h3>' . $this->app->getDef('module_apptest_title') . '</h3>';
        
                    $optional_rel_prods_content .= '  <div id="products" class="row list-group" itemtype="http://schema.org/ItemList">';
                    while ($Qrelated->fetch()) {                
                    if (OSCOM_APP_TEST_APPTEST_SHOW_QUANTITY == 'True')
                        $products_qty_slave = '<span itemprop="inventoryLevel">' . $Qrelated->value('products_quantity') . '</span>';
                    
                    $optional_rel_prods_content .= '  <div class="item col-sm-' .  $product_width . ' grid-group-item" itemprop="itemListElement" itemscope="" itemtype="http://schema.org/Product">';
                    $optional_rel_prods_content .= '    <meta itemprop="url" content="' . OSCOM::link('product_info.php', 'products_id=' . $Qrelated->value('pop_products_id_slave')) . '" />';


                    
                    $optional_rel_prods_content .= '    <div class="productHolder equal-height text-center">';

                    // show thumb image if Enabled
                    if (OSCOM_APP_TEST_APPTEST_SHOW_THUMBS == 'True') {
                        $optional_rel_prods_content .= '<a href="' . OSCOM::link('product_info.php', 'products_id=' . $Qrelated->value('pop_products_id_slave')) . '">' . HTML::image('images/' . $Qrelated->value('products_image'), $Qrelated->value('products_name'), SMALL_IMAGE_WIDTH, SMALL_IMAGE_HEIGHT) . '</a>';
                    }
      
                    $optional_rel_prods_content .= '      <div class="caption">';

                    if (OSCOM_APP_TEST_APPTEST_SHOW_NAME == 'True') {
                        $optional_rel_prods_content .= '<h2 class="group inner list-group-item-heading"><a href="' . OSCOM::link('product_info.php', 'products_id=' . $Qrelated->value('pop_products_id_slave')) . '"><span itemprop="name">' . $this->osc_truncate_text_rel_prod($Qrelated->value('products_name'), OSCOM_APP_TEST_APPTEST_NAME_LENGTH, OSCOM_APP_TEST_APPTEST_MAX_WORD_LENGTH) . '</span></a></h2 >';
                    }
                    
                    if (OSCOM_APP_TEST_APPTEST_SHOW_MODEL == 'True') {
                        $optional_rel_prods_content .=  '<p class="text-center small" itemprop="model">' . $Qrelated->value('products_model') . '</p>';
                    }
                    
                    $optional_rel_prods_content .= '<hr>';
                
            if (OSCOM_APP_TEST_APPTEST_SHOW_DESCRIPTION == 'True') {
                        $optional_rel_prods_content .= '<p class="text-center"><span itemprop="description">' . $this->osc_truncate_text_rel_prod(strip_tags($Qrelated->value('products_description')), OSCOM_APP_TEST_APPTEST_DESCRIPTION_LENGTH, OSCOM_APP_TEST_APPTEST_MAX_WORD_LENGTH) . '</span><br>';
            }

            if (OSCOM_APP_TEST_APPTEST_SHOW_PRICE == 'True') {
                $optional_rel_prods_content .= '<p class="text-center" itemprop="offers" itemscope itemtype="http://schema.org/Offer"><meta itemprop="priceCurrency" content="' . HTML::outputProtected($currency) . '">';
                if (tep_not_null($Qrelated->value('specials_products_price'))) {
                    $optional_rel_prods_content .= '<del>' . $currencies->display_price($Qrelated->value('products_price'), tep_get_tax_rate($Qrelated->value('products_tax_class_id'))) . '</del>&nbsp;';
                    $optional_rel_prods_content .= '<span class="productSpecialPrice" itemprop="price" content="' . $currencies->display_raw($Qrelated->value('specials_products_price'), tep_get_tax_rate($Qrelated->value('products_tax_class_id'))) . '">' . $currencies->display_price($Qrelated->value('specials_products_price'), tep_get_tax_rate($Qrelated->value('products_tax_class_id'))) . '</span><br>';
                } else {
                $optional_rel_prods_content .= '<span itemprop="price" content="' . $currencies->display_raw($Qrelated->value('products_price'), tep_get_tax_rate($Qrelated->value('products_tax_class_id'))) . '">' . $currencies->display_price($Qrelated->value('products_price'), tep_get_tax_rate($Qrelated->value('products_tax_class_id'))) . '</span><br>';
                }
                $optional_rel_prods_content .= '</p>';
            }
                    
                    if (OSCOM_APP_TEST_APPTEST_SHOW_QUANTITY == 'True') {
                        $optional_rel_prods_content .= '<p class="text-center" itemprop="offers" itemscope itemtype="http://schema.org/Offer">' . $this->app->getDef('module_apptest_products_quantity') . ' ' . $products_qty_slave . '</p>';
                    }
      
                    $optional_rel_prods_content .= '      </div>'; // caption
                    $optional_rel_prods_content .= '    </div>'; // thumbnail
                    $optional_rel_prods_content .= '  </div>'; // col-sm-' .  $product_width . '"
                }
                
                $optional_rel_prods_content .= '  </div>'; // end list of products
            }

            ob_start();
        include(__DIR__ . '/templates/APPTEST.php');
        $template = ob_get_clean();

        $oscTemplate->addContent($template, $this->group);
    }
    
    function isEnabled() {
      return $this->enabled;
    }

    function check() {
      return defined('OSCOM_APP_TEST_APPTEST_STATUS');
    }

    function install() {
      $this->app->redirect('Configure&Install&module=APPTEST');
    }

    function remove() {
      $this->app->redirect('Configure&Uninstall&module=APPTEST');
    }

    function keys() {
    }
    
        protected function osc_limit_text_rel_prod ($text, $maxchar, $wordlength = 40) {
        $text = str_replace ("\n", ' ', $text);
        $text = str_replace ("\r", ' ', $text);
        $text = str_replace ('<br>', ' ', $text);
        $text = wordwrap ($text, $wordlength, ' ', true);
        $text = preg_replace ("/[ ]+/", ' ', $text);
        $text_length = strlen ($text);
        $text_array = explode (" ", $text);

        $newtext = '';
        for ($array_key = 0, $length = 0; $length <= $text_length; $array_key++) {
            $length = strlen ($newtext) + strlen ($text_array[$array_key]) + 1;
            if ($length > $maxchar) break;
            $newtext = $newtext . ' ' . $text_array[$array_key];
        }

        return $newtext;
    }
    
    protected function osc_truncate_text_rel_prod ($products_text, $maxchar, $wordlength = 40) {
        $products_text = ($products_text);
            if ($maxchar > 0) {
                $products_text_length = strlen ($products_text);
                if ($products_text_length > $maxchar) {
                    $products_text = $this->osc_limit_text_rel_prod ($products_text, $maxchar, $wordlength);
                    $products_text .= '&nbsp;...';
                }
            }
                
            return $products_text;
   }
   
   
  }
?>

We also need a minor change to the language definition file for the module, so open up OSC/Apps/Test/Test/languages/english/modules/APPTEST/APPTEST.php

and replace the content with this

module_apptest_title = Related Products    
module_apptest_short_title = Related Products

module_apptest_legacy_admin_app_button = Manage App

module_apptest_introduction = This module will show the related products on the product info page
module_apptest_products_quantity = Qty Available:

Now you can go to Apps -> Test App -> Configure and install the content module. If it was already installed, uninstall it using the orange Uninstall... button on the bottom right then reinstall it. If you haven't yet related some products, go to Apps -> Test App -> Admin and do that now. Then navigate to the master product's page and you should see the related product/s.

Next, we'll upgrade the admin page so that we can edit, delete, reciprocate, and inherit related products.

 

Edit: fixed dumb spelling mistakes :)


Edited by frankl, 22 March 2017 - 22:04.

Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#23   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 22 March 2017 - 22:20

One small change to the OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates/admin.php page which fixes pagination.

 

Change this

$Qrelated->getPageSetLinks();

to this

$Qrelated->getPageSetLinks(tep_get_all_get_params(array('page')));

Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#24   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 23 March 2017 - 06:25

This Related Products app is now complete, with full admin where you can add, edit, reciprocate, inherit, and delete related products.

 

If a dumb cut and paste coder like me can make an app, just about anyone can.

 

Please use this app (along with this thread and the developer docs https://library.osco...evelopers&apps) as a guide to make your own apps. If you do, please post them so everyone can see what is possible.

 

Check it out on Github -> https://github.com/f...ommerce-2.4-App

 

It will take you less than a minute to download, upload to your osCommerce 2.4 site, and install. Seriously! :thumbsup:


Edited by frankl, 23 March 2017 - 06:25.

Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#25   burt

burt

    I drink and I know things

  • Community Team
  • 12,463 posts
  • Real Name:G Burton
  • Gender:Male
  • Location:UK/DEV/on

Posted 23 March 2017 - 10:32

Awesome work @frankl - thank you !


This is a signature that appears on all my posts.  It is not specifically aimed at you.

 

IF YOU MAKE A POST REQUESTING HELP...please state the exact version of osCommerce that you are using. THANKS
 
If you are still on the old style osCommerce, it is time to move to Responsive.

 


#26   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 24 March 2017 - 02:09

Thanks Burt!


Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#27   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 24 March 2017 - 02:20

Did you say you want a hook for that?

 

Attached File  page_tab.gif   22.46KB   0 downloads

We can do that, but first we need to make a couple of minor changes to the categories.php page because it's not properly set up to take new page tabs.

in catalog/admin/categories.php change this
 

<div class="row">
  <div id="productLanguageTabs">
    <ul class="nav nav-tabs">

                
to this
 

<div class="row">
  <div id="productLanguageTabs">
    <ul class="nav nav-tabs2">

and this
 

<?php
      for ($i=0, $n=sizeof($languages); $i<$n; $i++) {
        echo '<li ' . ($i === 0 ? 'class="active"' : '') . '><a data-target="#section_general_content_' . $languages[$i]['directory'] . '" data-toggle="tab">' . $OSCOM_Language->getImage($languages[$i]['code']) . '&nbsp;' . $languages[$i]['name'] . '</a></li>';
      }
?>

                </ul>

                <div class="tab-content">

                
to this
 

<?php
      for ($i=0, $n=sizeof($languages); $i<$n; $i++) {
        echo '<li ' . ($i === 0 ? 'class="active"' : '') . '><a data-target="#section_general_content_' . $languages[$i]['directory'] . '" data-toggle="tab">' . $OSCOM_Language->getImage($languages[$i]['code']) . '&nbsp;' . $languages[$i]['name'] . '</a></li>';
      }
?>

                </ul>

                <div class="tab-content2">

All we've done is add the number 2 next to nav-tabs and tab-content.
               
We need to do that otherwise the extra tab that gets added in gets added in twice and screws up the display.

Perhaps @Harald Ponce de Leon will change this in the future to allow admin/products tab hooks. Or maybe I've done it the wrong way?

We need to tell osCommerce that we are introducing hooks into the categories page, so go back to the trusty oscommerce.json file and put in the required information.

Change this
 

    "modules": {
        "AdminMenu": {
            "Test":    "Module\\Admin\\Menu\\Test"
        },
        "Content": {
            "product_info": {
                "PIRELATED":    "Module\\Content\\PIRELATED"
            }

        }
    },

to this        
 

    "modules": {
        "AdminMenu": {
            "Related":    "Module\\Admin\\Menu\\Related"
        },
        "Content": {
            "product_info": {
                "PIRELATED":    "Module\\Content\\PIRELATED"
            }
        },
        "Hooks": {
            "Admin/Products": {
                "Action":    "Module\\Hooks\\Admin\\Products\\Action",
                "Page":        "Module\\Hooks\\Admin\\Products\\PageTab"
            }
        }

    },

    
Note: I'm now altering the files from the Github repository, so the Vendor, App, and Module names have changed from the posts above.

Now to create the actual hook, make a new folder OSC/Apps/FrankL/Related/Module/Hooks/Admin/Products

In that folder, create the Action file, Action.php
 

<?php
/**
  * Related Products App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 Frank Ludriks; https://www.ink-cartridge.com.au
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\FrankL\Related\Module\Hooks\Admin\Products;

use OSC\OM\HTML;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

use OSC\Apps\FrankL\Related\Related as RelatedApp;

class Action implements \OSC\OM\Modules\HooksInterface
{
    protected $app;
    protected $ms;

    public function __construct()
    {
        if (!Registry::exists('Related')) {
            Registry::set('Related', new RelatedApp());
        }

        $this->app = Registry::get('Related');

        $this->ms = Registry::get('MessageStack');

        $this->app->loadDefinitions('hooks/admin/products/action');
    }

    public function execute()
    {

    
    }


}


and also a file PageTab.php
 

<?php
/**
  * Related Products App for osCommerce Online Merchant
  *
  * @[member='copyright'] (c) 2016 Frank Ludriks; https://www.ink-cartridge.com.au
  * @[member='licensed2kill'] MIT; https://www.oscommerce.com/license/mit.txt
  */

namespace OSC\Apps\FrankL\Related\Module\Hooks\Admin\Products;

use OSC\OM\HTML;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

use OSC\Apps\FrankL\Related\Related as RelatedApp;

class PageTab implements \OSC\OM\Modules\HooksInterface
{
    protected $app;

    public function __construct()
    {
        if (!Registry::exists('Related')) {
            Registry::set('Related', new RelatedApp());
        }

        $this->app = Registry::get('Related');
    }

    public function display()
    {

        if (!defined('OSCOM_APP_FRANKL_RELATED_STATUS')) {
            return false;
        }

        $this->app->loadDefinitions('hooks/admin/products/tab');

        $output = '';

        $status = [];
        
        $info = '';
        
        $data = '';

        
        //check for masters
            $Qcheck = $this->app->db->prepare('select * from :table_products_related_products where pop_products_id_master = :products_id and pop_products_id_slave != :products_id');
            $Qcheck->bindInt(':products_id', $_GET['pID']);
            $Qcheck->execute();
            if ($Qcheck->fetch() !== false) {
                $master_query = 'select pop_products_id_slave
                                  from :table_products_related_products
                                  where pop_products_id_master = :products_id';
                $Qmaster = $this->app->db->prepare($master_query);
                $Qmaster->bindInt(':products_id', $_GET['pID']);
                $Qmaster->execute();
                
                $info .=  '<div class="col-sm-5"><table class="table table-hover">' .
                            ' <thead>' .
                            '    <tr class="info">' .
                            '      <th>' . $this->app->getDef('heading_master') . '</th>' .
                            '    </tr>' .
                            '  </thead>';
                
                  while ($Qmaster->fetch()) {
                
                    $related_query = 'select pa.*, pd.products_id, pd.products_name
                                      from :table_products_related_products pa
                                      left join :table_products_description pd
                                      on pa.pop_products_id_slave = pd.products_id
                                      and pd.language_id = :languages_id
                                      left join :table_products p
                                      on p.products_id = pd.products_id
                                      where pd.products_id = :products_id';
                    $Qrelated = $this->app->db->prepare($related_query);
                    $Qrelated->bindInt(':products_id', $Qmaster->value('pop_products_id_slave'));
                    $Qrelated->bindInt(':languages_id', $this->app->lang->getId());
                    $Qrelated->execute();
                  
                     while ($Qrelated->fetch()) {
                        $info .= '  <tr>' .
                            '    <td>' .
                            '      ' . $Qrelated->value('products_name') .
                            '   </td>' .
                            '  </tr>';
                    }
                   
                  }
                
                $info .=  '</table></div>';
                
            } else {
                
                $info .=    '<div class="col-sm-5"><table class="table table-hover">' .
                            ' <thead>' .
                            '    <tr class="info">' .
                            '      <th>' . $this->app->getDef('heading_master') . '</th>' .
                            '    </tr>' .
                            '  </thead>' .
                            '   <tr>' .
                            '      <td>' .
                            '        <div class="alert alert-warning col-sm-5" role="alert">' . $this->app->getDef('no_products') . '</div>' .
                            '      </td>' .
                            '    </tr>' .
                            '</table></div>';
                    
            }
                
        //check for slaves
                
            $Qcheck = $this->app->db->prepare('select * from :table_products_related_products where pop_products_id_slave = :products_id');
            $Qcheck->bindInt(':products_id', $_GET['pID']);
            $Qcheck->execute();
              if ($Qcheck->fetch() !== false) {
                $slave_query = 'select pop_products_id_master
                                from :table_products_related_products
                                where pop_products_id_slave = :products_id';
                $Qslave = $this->app->db->prepare($slave_query);
                $Qslave->bindInt(':products_id', $_GET['pID']);
                $Qslave->execute();
                
                
                $info .=  '<div class="col-sm-5"><table class="table table-hover">' .
                            ' <thead>' .
                            '    <tr class="info">' .
                            '      <th>' . $this->app->getDef('heading_slave') . '</th>' .
                            '    </tr>' .
                            '  </thead>';
                
                
                  while ($Qslave->fetch()) {
                
                    $related_query = 'select pa.*, pd.products_id, pd.products_name
                                      from :table_products_related_products pa
                                      left join :table_products_description pd
                                      on pa.pop_products_id_master = pd.products_id
                                      and pd.language_id = :languages_id
                                      left join :table_products p
                                      on p.products_id = pd.products_id
                                      where pd.products_id = :products_id and pa.pop_products_id_slave = :pop_products_id_slave';
                    $Qrelated = $this->app->db->prepare($related_query);
                    $Qrelated->bindInt(':products_id', $Qslave->value('pop_products_id_master'));
                    $Qrelated->bindInt(':pop_products_id_slave', $_GET['pID']);
                    $Qrelated->bindInt(':languages_id', $this->app->lang->getId());
                    $Qrelated->execute();

                     while ($Qrelated->fetch()) {
                        $info .= '  <tr>' .
                                 '    <td>' .
                                 '      ' . $Qrelated->value('products_name') .
                                 '   </td>' .
                                 '  </tr>';
                     
                     }
                  
                  }
                    
                    $info .=  '</table></div>';
                   
                   } else {
                $info .=    '<div class="col-sm-5"><table class="table table-hover">' .
                            ' <thead>' .
                            '    <tr class="info">' .
                            '      <th>' . $this->app->getDef('heading_slave') . '</th>' .
                            '    </tr>' .
                            '  </thead>' .
                            '   <tr>' .
                            '      <td>' .
                            '        <div class="alert alert-warning col-sm-5" role="alert">' . $this->app->getDef('no_products') . '</div>' .
                            '      </td>' .
                            '    </tr>' .
                            '</table></div>';
                    
              }
                
                

                $related_button = HTML::button($this->app->getDef('button_manage'), 'fa fa-arrows-h', $this->app->link('Admin&products_id_view=' . $_GET['pID']), null, 'btn-info');
                
                $data .= '<div class="panel panel-info oscom-panel">' .
                        '  <div class="panel-body">' .
                        '    <div class="container-fluid">' .
                        '      <div class="row">' .
                        '        ' . $info .
                        '      </div>' .
                        '      <div class="text-center">' .
                        '        ' . $related_button .
                        '      </div>' .
                        '    </div>' .
                        '  </div>' .
                        '</div>';
                
                $tab_title = addslashes($this->app->getDef('tab_title'));

                $output = <<<EOD
<div id="section_relatedAppRelated_content" class="tab-pane oscom-m-top-15">
  {$data}
</div>

<script>
$('#section_relatedAppRelated_content').appendTo('#productTabs .tab-content');
$('#productTabs .nav-tabs').append('<li><a data-target="#section_relatedAppRelated_content" data-toggle="tab">{$tab_title}</a></li>');
</script>
EOD;

        return $output;
    }


}


As usual, we need to create language definition files, so you need to create a new folder OSC/Apps/FrankL/Related/languages/english/hooks/admin/products
                                                    
and in that folder create a file tab.txt with the following contents
 

button_manage = Manage Related Products
tab_title = Related Products
no_products = None
heading_master = Master of...
heading_slave = Slave of...

That's it. Now go to a product edit page and you'll have a new tab called Related Products.

You can also check to see which hooks are installed by selecting Legacy -> Modules -> Hooks


Edited by frankl, 24 March 2017 - 02:23.

Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#28   clustersolutions

clustersolutions
  • Community Sponsor
  • 453 posts
  • Real Name:Tim
  • Gender:Male
  • Location:Los Angeles

Posted 26 March 2017 - 06:23

@frankl, awesome, go for the github, I can help you test...pls LMK...Tim



#29   clustersolutions

clustersolutions
  • Community Sponsor
  • 453 posts
  • Real Name:Tim
  • Gender:Male
  • Location:Los Angeles

Posted 26 March 2017 - 06:24

Oh, shoot...you did...awesome. Thx!



#30 ONLINE   Gergely

Gergely

    Json Juggler

  • Community Team
  • 2,114 posts
  • Real Name:Gergely Tóth
  • Gender:Male
  • Location:Budapest

Posted 26 March 2017 - 11:30

@frankl

		$data = '<div id="section_relatedAppRelated_content" class="tab-pane oscom-m-top-15">' . $data . '</div>';

                $tab_title = addslashes($this->app->getDef('tab_title'));

                $output = <<<EOD
<script>
$('{$data}').insertAfter('div#section_images_content');

$('#productTabs ul:first').append('<li><a data-target="#section_relatedAppRelated_content" data-toggle="tab">{$tab_title}</a></li>');
</script>
EOD;

:thumbsup:


Next PHP changes will kill the current codes on the following years. We should do programing for the future and never stick in the present.

My addons: Conversion Tools::Hungarian Translation::Email Templates::URL redirection
 
Development Works: Setup Languages::Email Templates::Languages from ini files::Parcel Shops::Facebook App
 
What core codes have been complained?

In orders table payment_methods value would be better if payment class name used than payment's language name.
In the orders class we found order status does not contains $order->info['orders_status'] but instead there is $order->info['orders_status_name'], and that property is language dependant.
We can not identify in order the customer language.


#31 ONLINE   wHiTeHaT

wHiTeHaT
  • Community Team
  • 1,143 posts
  • Real Name:Henry
  • Gender:Male
  • Location:Netherlands

Posted 26 March 2017 - 12:49

@Gergely, now load a product where master or slave is like like Bug's life.
requires to sanitize $data before inserted to the dom
 
quick solution:
line 86:

							'	  ' . addslashes($Qrelated->value('products_name')) .

or

		$data = '<div id="section_relatedAppRelated_content" class="tab-pane oscom-m-top-15">' . addslashes($data) . '</div>';


Edited by wHiTeHaT, 26 March 2017 - 12:52.

Do you need an osCommerce website? Do you want to have an Responsive osCommerce CONTACT ME as i am for HIRE

#32   clustersolutions

clustersolutions
  • Community Sponsor
  • 453 posts
  • Real Name:Tim
  • Gender:Male
  • Location:Los Angeles

Posted 26 March 2017 - 18:54

@frankl, all is working, easy peasy, I need to buy u a beer! Thx!

 

Can't wait to see the full app market place roll out. It's gonna be good! And this doesn't have the resource hog issues like other carts that I dislike.



#33   frankl

frankl

    One of the originals...

  • Community Sponsor
  • 475 posts
  • Real Name:Frank
  • Gender:Male
  • Location:Sydney, Australia

Posted 26 March 2017 - 23:08

@Gergely

@wHiTeHaT

 

Thanks for that. Github updated with Gergely and Henry's changes.

 

@clustersolutions

 

Thanks for testing. Beer welcome if we ever meet ;)


Let's make things easier for new osCommerce users http://forums.oscomm...bles/?p=1718900  Getting there with osCommerce 2.4! :thumbsup:


#34   piernas

piernas
  • Members
  • 462 posts
  • Real Name:Juanma
  • Gender:Male
  • Location:Madrid

Posted Yesterday, 19:37

@frankl you should actually use a real Vendor and App name for your example instead of Test Test, and make a note that developers must use their own Vendor and App code names (registrations will open for this when the Apps Marketplace goes live).

 

There is also a Content Module hook you can define in your json metadata file. This should automatically load your module - you just need to take care of an administration page if parameters are to be set.

 

In case anyone has missed it, some documentation is up at:

 

https://library.osco...developers

 

 

I'm curious on how this will work. Will still be allowed to just upload an app via ftp or will be mandatory to download and install them via the marketplace?