Latest News: (loading..)
frankl

How to make a 2.4 App

41 posts in this topic

I'm going to attempt to make an app, and I'm going to post my methods here so that others can follow along and hopefully join in. With a bit of luck @@Harald Ponce de Leon and others better at PHP coding will see this and make any corrections or give some help where needed.

 

Of course, all this is speculative as we don't know what the final form of oSC 2.4 will be, but as the Paypal App is in there and presumably is more or less complete we can use that as a template.

 

I am going to make a Related Products App, for two reasons. It is fairly simple, but also involves configuration, admin pages, and at least one content module.

 

OK, here goes:

 

First, let's get make the most basic module possible, one that put some static content on the product_info page.

Create a new directory in catalog\includes\OSC\Apps named Test

Then create a directory structure off that, so that you have

 

 

 

includes
    |_ OSC
        |_ Apps
            |_ Test
                |_ Test
                    |_ Module
                        |_ Content
                            |_ templates

 

 

 

In OSC\Apps\Test\Test, create a file named oscommerce.json

This will tell osCommerce where to find the app, who wrote it, and where the modules and routes can be found.

In the oscommerce.json file we will be giving our app the name of Test, and the content module, which shall be called APPTEST, will belong to the product_info group.

Put the following in this file and save it

{
    "title":     "Test App",
    "app":       "Test",
    "vendor":    "Test",
    "version":    "0.0.1",
    "req_core_version":    "2.4",
    "license":    "MIT",
    "authors": [
        {
            "name":        "Author",
            "company":    "Company",
            "email":    "email@[member=Example].com",
            "website":    "http://www.example.com"
        }
    ],
    "modules": {
        "Content": {
            "product_info": {
                "APPTEST":    "Module\\Content\\APPTEST"
            }
        }
    }
}

Create another file in the OSC\Apps\Test\Test directory called Test.php. This will initiate the Test class so it can be used by osCommerce. For now it won't do anything, but we'll add some functions to it later.

Put the following in this file

<?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;

class Test extends \OSC\OM\AppAbstract
{

    protected function init()
    {
    }

  }
?>

Now we will create the APPTEST module in OSC/Apps/Test/Test/Module/Content. This app will add some static (for now) content to the product info page. Create a file in this directory called APPTEST.php. I'm not sure if capitalization of the filename is important but as that's what the Paypal app does I'm going to do it too.

Add to this file:

<?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\FrankL\Test\Module\Content;

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

    function __construct() {

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

      $this->title = 'Test App';
      $this->description = 'Just a test.';
      $this->sort_order = '10';
      
      $this->enabled = true;
    }

    function execute() {
      global $oscTemplate;


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

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

    function check() {
    }

    function install() {
    }

    function remove() {
    }

    function keys() {
    }
  }
?>

We also need a template to display the actual content, so create a file in OSC/Apps/Test/Test/Module/Content/templates also called APPTEST.php and put the following content in it. This is just placeholder content, we'll change that to dynamically generated content later.

  <br />
  <div itemscope itemtype="http://schema.org/ItemList">
    <meta itemprop="itemListOrder" content="http://schema.org/ItemListUnordered" />
    <meta itemprop="numberOfItems" content="6" />

    <h3 itemprop="name">Test Content for Test App</h3>

    <div class="row">
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=23"><img src="images/gt_interactive/wheel_of_time.gif" alt="The Wheel Of Time" title="The Wheel Of Time" width="100" height="80" /></a>
             <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=23"><span itemprop="name">The Wheel Of Time</span></a><meta itemprop="position" content="1" /></h5>    
          </div>  
        </div>
      </div>
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=1"><img src="images/matrox/mg200mms.gif" alt="Matrox G200 MMS" title="Matrox G200 MMS" width="100" height="80" /></a>    
          <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=1"><span itemprop="name">Matrox G200 MMS</span></a><meta itemprop="position" content="2" /></h5>    
          </div>  
        </div>
      </div>
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=2"><img src="images/matrox/mg400-32mb.gif" alt="Matrox G400 32MB" title="Matrox G400 32MB" width="100" height="80" /></a>    
          <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=2"><span itemprop="name">Matrox G400 32MB</span></a><meta itemprop="position" content="3" /></h5>    
          </div>  
        </div>
      </div>
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=25"><img src="images/microsoft/intkeyboardps2.gif" alt="Microsoft Internet Keyboard PS/2" title="Microsoft Internet Keyboard PS/2" width="100" height="80" /></a>    
          <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=25"><span itemprop="name">Microsoft Internet Keyboard PS/2</span></a><meta itemprop="position" content="4" /></h5>    
          </div>  
        </div>
      </div>
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=26"><img src="images/microsoft/imexplorer.gif" alt="Microsoft IntelliMouse Explorer" title="Microsoft IntelliMouse Explorer" width="100" height="80" /></a>    
          <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=26"><span itemprop="name">Microsoft IntelliMouse Explorer</span></a><meta itemprop="position" content="5" /></h5>    
          </div>  
        </div>
      </div>
      <div class="col-sm-6 col-md-4">  
        <div class="thumbnail"><a href="product_info.php?products_id=27"><img src="images/hewlett_packard/lj1100xi.gif" alt="Hewlett Packard LaserJet 1100Xi" title="Hewlett Packard LaserJet 1100Xi" width="100" height="80" /></a>    
          <div class="caption"><h5 class="text-center" itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"><a itemprop="url" href="product_info.php?products_id=27"><span itemprop="name">Hewlett Packard LaserJet 1100Xi</span></a><meta itemprop="position" content="6" /></h5>    
          </div>  
        </div>
      </div>    
    </div>
  </div>

That's our basic content created, but it won't show on the product info page until it gets installed.

The app is not yet installed, but if you want to see if the module works so far go to phpMyAdmin and search for the MODULE_CONTENT_INSTALLED configuration_key in the configuration table. Add at the end of the list of modules installed

;product_info/Test\Test\APPTEST

then navigate to product_info.php where you should see the Test App's contents.

 

Next I'm going to attempt to create an admin menu and installation and app configuration pages.

John W, beerbee, raiwa and 4 others like this

Share this post


Link to post
Share on other sites

@@frankl I'm looking forward to following this thread Frank. Are you open to questions as you go along?

 

Dan

Share this post


Link to post
Share on other sites

@@Dan Cole

 

Open to questions Dan, not sure if I will be able to answer them :D

Share this post


Link to post
Share on other sites

@@Dan Cole

 

Open to questions Dan, not sure if I will be able to answer them :D

 

I can relate to that...the new app process raises lots of questions....I'm sure you'll be able this one.

 

Why the FrankL in the namespace declaration?

 

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

 

It's not in your directory path anywhere that I can see.  Remember you're talking to a guy who knows nothing about namespaces except that Harald seems to like them. :)

 

Dan

Share this post


Link to post
Share on other sites

@@Dan Cole

 

Got my apps mixed up -- that line should be namespace OSC\Apps\Test\Test\Module\Content

 

For those following along, please change it in your file.

Share this post


Link to post
Share on other sites

Posted (edited)

@@Dan Cole

 

Got my apps mixed up -- that line should be namespace OSC\Apps\Test\Test\Module\Content

 

For those following along, please change it in your file.

 

Thanks for that Frank....now I understand. :D

 

To get myself up to speed on namespaces I googled for it and found this nice explanation.

 

Dan

 

PS:  I actually understood most of it. :wacko:

Edited by Dan Cole
John W likes this

Share this post


Link to post
Share on other sites

@@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.oscommerce.com/Online&en&oscom_2_4&developers&apps

Share this post


Link to post
Share on other sites

@@frankl

Nice done. Good work,  you should be start to get a teacher. You save some people a lot of time.

Share this post


Link to post
Share on other sites

As @@Harald Ponce de Leon said, when you go to make an actual app you will need vendor and app names to be your own, don't use Test for either unless it's strictly for testing purposes. Once this app is complete it will be packaged up under a different Vendor and App name.

 

Apps rely on a single admin page to administer the app. In this case it is a file called Home.php, with several controllers that take the required actions. You can think of it like php pages that show different things depending on the URL such as the categories.php page which has different actions depending on the variables (i.e. https://www.example.com.au/admin/categories.php?cPath=2_19&pID=22&action=new_product will edit Unreal Tournament in a fresh installation)

However the admin page does not have all the coding on one page like categories.php, rather it has separate classes in an Actions folder which take care of installation, configuration, and administration.

The main admin page also has templates which it accesses depending upon what actions the page needs to undertake.

We tell osCommerce which admin page to use through the oscommerce.json file in the "routes" section.

Apps also need an admin menu, which we also notify osCommerce of using another section in oscommerce.json called "AdminMenu".

Change oscommerce.json from
 

{
    "title":     "Test App",
    "app":       "Test",
    "vendor":    "Test",
    "version":    "0.0.1",
    "req_core_version":    "2.4",
    "license":    "MIT",
    "authors": [
        {
            "name":        "Author",
            "company":    "Company",
            "email":    "email@[member=Example].com",
            "website":    "http://www.example.com"
        }
    ],
    "modules": {
        "Content": {
            "product_info": {
                "APPTEST":    "Module\\Content\\APPTEST"
            }
        }
    }
}


to
 

{
    "title":     "Test App",
    "app":       "Test",
    "vendor":    "Test",
    "version":    "0.0.1",
    "req_core_version":    "2.4",
    "license":    "MIT",
    "authors": [
        {
            "name":        "Author",
            "company":    "Company",
            "email":    "email@[member=Example].com",
            "website":    "http://www.example.com"
        }
    ],
    "modules": {
        "AdminMenu": {
            "Test":    "Module\\Admin\\Menu\\Test"
        },
        "Content": {
            "product_info": {
                "APPTEST":    "Module\\Content\\APPTEST"
            }
        }
},
    "routes": {
        "Admin":    "Sites\\Admin\\Pages\\Home",
        "Shop": {
        }
    }
}

We've added an AdminMenu entry and a Routes/Admin entry

Apps also load their own language definitions, found in OSC/Apps/Test/Test/languages/YOUR LANGUAGE HERE, so go ahead and create that folder now (I used english, so it's OSC/Apps/Test/Test/languages/english).

To make an admin menu, create a folder OSC/Apps/Test/Test/Module/Admin/Menu

In that folder, create a file called Test.php and add
 

<?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\Menu;

use OSC\OM\Registry;

use OSC\Apps\Test\Test\Test as TestApp;

class Test implements \OSC\OM\Modules\AdminMenuInterface
{
    public static function execute()
    {
        if (!Registry::exists('Test')) {
            Registry::set('Test', new TestApp());
        }

        $OSCOM_Test = Registry::get('Test');

        $OSCOM_Test->loadDefinitions('admin/modules/boxes/test');

        $test_menu = [
            [
                'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                'title' => $OSCOM_Test->getDef('module_admin_menu_install'),
                'link' => $OSCOM_Test->link()
            ]
        ];

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);
    }
}


You may notice that the menu file makes use of aliasing, in this line use OSC\Apps\Test\Test\Test as TestApp;

All that means is that we are telling osCommerce that the full path to the class is OSC\Apps\Test\Test\Test (.php extension assumed) but it can reference it with the shorter alias of Testapp.

You can also see a new function loadDefinitions, which will grab the language defines for the menu from the file admin/modules/boxes/test (assumes .txt so you don't need to add that) in whatever language your admin is set to (in this case, we only have english)

The accompanying language file will be created in OSC/Apps/Test/Test/languages/english/admin/modules/boxes

In that folder create a file test.txt and add:

module_admin_menu_title = Test App
module_admin_menu_install = Install


At the moment we just have Install, we'll add more menu definitions later.

The link shown in the line 'link' => $OSCOM_Test->link() is the one we defined earlier in the oscommerce.json file under routes, Sites\\Admin\\Pages\\Home

That file, Home.php, is created in a folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home

Put in that file
 

<?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\Sites\Admin\Pages\Home;

use OSC\OM\Apps;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

use OSC\Apps\Test\Test\Test;

class Home extends \OSC\OM\PagesAbstract
{
    public $app;

    protected function init()
    {
       
         $OSCOM_Test = new Test();
        Registry::set('Test', $OSCOM_Test);

        $this->app = $OSCOM_Test;
        
        $this->app->loadDefinitions('admin/install');
        
    }
}


This file on it's own won't do much.

We do have to make some more definitions though, it's asking for language definitions from admin/install so in the OSC/Apps/Test/Test/languages/english/admin folder create a file named install.txt (remembering that in the line $this->app->loadDefinitions('admin/install'); a .txt extension is assumed)

Put in that file
 

onboarding_intro_title = Related Products
onboarding_intro_body = <p>Manage related products and display in your store to drive further sales</p><p style="text-align: center;">{{button_install}}</p>

button_install = Install Now

We'll use those definitions in a template file in a moment.

We now need templates for the Home.php page to use. These go in the folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates

template_top.php
 

<?php
use OSC\OM\HTML;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

$OSCOM_Test = Registry::get('Test');
$OSCOM_Page = Registry::get('Site')->getPage();
?>

<div class="row" style="padding-bottom: 30px;">

  <div class="col-sm-6 text-right text-muted">
    <?= $OSCOM_Test->getTitle() . ' v' . $OSCOM_Test->getVersion(); ?>
  </div>
</div>

<?php
if ($OSCOM_MessageStack->exists('Test')) {
    echo $OSCOM_MessageStack->get('Test');
}
?>

template_bottom.php can be blank

main.php is used to display the content when we select Install from the admin menu. Add to that file

<?php
use OSC\OM\HTML;

require(__DIR__ . '/template_top.php');
?>

<div class="row">
  <div class="col-sm-6">
    <div class="panel panel-primary">
      <div class="panel-heading"><?= $OSCOM_Test->getDef('onboarding_intro_title'); ?></div>
      <div class="panel-body">
        <?=
            $OSCOM_Test->getDef('onboarding_intro_body', [
                'button_install' => HTML::button($OSCOM_Test->getDef('button_install'), null, $OSCOM_Test->link(''), null, 'btn-primary')
            ]);
        ?>
      </div>
    </div>
  </div>
</div>

<?php
require(__DIR__ . '/template_bottom.php');
?>

At the moment the button (and page) don't do anything, but we'll work on that next.

 

You should now have a new menu item in the drop down Apps menu in your 2.4 admin which says Test App, with a sub menu saying Install. Clicking that should take you through to the installation page.

beerbee likes this

Share this post


Link to post
Share on other sites

@@Harald Ponce de Leon

 


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.

 

 

Hi Harald, could you explain this one in a little more detail?
 

 

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

 

https://library.oscommerce.com/Online&en&oscom_2_4&developers&apps

 

 

Will there be additional documentation on Apps? That isn't comprehensive enough :)

Share this post


Link to post
Share on other sites

That's what I am talking about...tribal knowledge! Please keep it coming!

 

@@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.oscommerce.com/Online&en&oscom_2_4&developers&apps

Share this post


Link to post
Share on other sites

Next we need to set up some action pages so that Home.php actually does something.

Create a new folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions

Create a new file, one called Start.php

Start.php will install the app. You can create new database entries and and any database tables needed for the base app through this file.

Actually, Start.php will just start the processing:
 

<?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\Sites\Admin\Pages\Home\Actions;

class Start extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $this->page->data['action'] = 'Start';
    }
}

The grunt work is done by Process.php which is in the OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Start folder. Create that now.
 

<?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\Sites\Admin\Pages\Home\Actions\Start;

use OSC\OM\HTTP;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

class Process extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        if (!defined('OSCOM_APP_TEST_TEST_STATUS')) {
        $OSCOM_Db = Registry::get('Db');
        $OSCOM_Db->save('configuration', [
        'configuration_title' => 'Enable Related Products App',
        'configuration_key' => 'OSCOM_APP_TEST_TEST_STATUS',
        'configuration_value' => 'True',
        'configuration_description' => 'Should we install optional_related_products ?',
        'configuration_group_id' => '6',
        'sort_order' => '2',
        'set_function' => 'tep_cfg_select_option(array(\'True\', \'False\'), ',
        'date_added' => 'now()'
      ]);
        }

        $OSCOM_Test->redirect('Configure');
    }
}

Right now we are just adding a simple Yes/No is this app installed entry. Later we can use the Process.php file to create database tables and do other tasks if needed.

As you can see, once this file is processed it redirects to the Configure action. The Configure action is for configuring content, payment, shipping, or order total modules which come with the app.

Create Configure.php in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions (same directory as Start.php)
 

<?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\Sites\Admin\Pages\Home\Actions;

use OSC\OM\Registry;

class Configure extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_Test = Registry::get('Test');

        $this->page->setFile('configure.php');
        $this->page->data['action'] = 'Configure';

        $OSCOM_Test->loadDefinitions('admin/configure');

        $modules = $OSCOM_Test->getConfigModules();

        $default_module = 'APPTEST';

        foreach ($modules as $m) {
            if ($OSCOM_Test->getConfigModuleInfo($m, 'is_installed') === true ) {
                $default_module = $m;
                break;
            }
        }

        $this->page->data['current_module'] = (isset($_GET['module']) && in_array($_GET['module'], $modules)) ? $_GET['module'] : $default_module;

    }
}


Once again this file doesn't do much, the grunt work will be done in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Configure/Process.php, although as you can see it grabs a list of modules (which reside in the OSC/Apps/Test/Test/Module folder, under either Content, Hooks, Shipping, Payment etc subfolders) and checks to see if they are installed.

I've added a default module there, APPTEST, I'm not sure if that's necessary but the Paypal app had a default module so I've got one too :)

Here's the OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Configure/Process.php file
 

<?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\Sites\Admin\Pages\Home\Actions\Configure;

use OSC\OM\Registry;

class Process extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        $current_module = 'APPTEST';

        $m = Registry::get('TestAdminConfig' . $current_module);

        foreach ($m->getParameters() as $key) {
            $p = strtolower($key);

            if (isset($_POST[$p])) {
                $OSCOM_Test->saveCfgParam($key, $_POST[$p]);
            }
        }

        $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_cfg_saved_success'), 'success', 'Test');

        $OSCOM_Test->redirect('Configure&module=' . $current_module);
    }
}

See the getParameters function? That's quite important, I'll get back to that.

Before we go any further, we need to add some more language definitions, this time for the TESTAPP we created at the beginning.

The definition file will live in OSC/Apps/Test/Test/languages/english/modules/APPTEST

In that folder, create a new file called APPTEST.txt

Put in it:
 

module_apptest_title = Test
module_apptest_short_title = Test

module_apptest_legacy_admin_app_button = Manage App

module_apptest_introduction = This module will show the related products on the product info page

You also need to create an admin.txt file in OSC/Apps/Test/Test/languages/english to handle some generic language definitions.
 

app_link_info = Info/Help
app_link_privacy = Privacy
button_back = Back
button_cancel = Cancel
button_delete = Delete
button_dialog_delete = Delete …
button_install = Install
button_install_title = Install {{title}}
button_save = Save
button_uninstall = Uninstall
button_dialog_uninstall = Uninstall …
button_view = View

We also need a template file for the Configure.php action file

This lives in the same folder as template_top.php, template_bottom.php and main.php -- OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates

create a new file in this directory called configure.php.
 

<?php
use OSC\OM\HTML;
use OSC\OM\Registry;

$OSCOM_Page = Registry::get('Site')->getPage();

$current_module = $OSCOM_Page->data['current_module'];

$OSCOM_Test_Config = Registry::get('TestAdminConfig' . $current_module);

require(__DIR__ . '/template_top.php');
?>

<ul id="appTestToolbar" class="nav nav-pills" style="padding-bottom: 15px;">

<?php
foreach ($OSCOM_Test->getConfigModules() as $m) {
    if ($OSCOM_Test->getConfigModuleInfo($m, 'is_installed') === true) {
        echo '<li data-module="' . $m . '"><a href="' . $OSCOM_Test->link('Configure&module=' . $m) . '">' . $OSCOM_Test->getConfigModuleInfo($m, 'short_title') . '</a></li>';
    }
}
?>

  <li class="dropdown"><a class="dropdown-toggle" href="#" data-toggle="dropdown">Install <span class="caret"></span></a>
    <ul class="dropdown-menu">

<?php
foreach ($OSCOM_Test->getConfigModules() as $m) {
    if ($OSCOM_Test->getConfigModuleInfo($m, 'is_installed') === false) {
        echo '<li><a href="' . $OSCOM_Test->link('Configure&module=' . $m) . '">' . $OSCOM_Test->getConfigModuleInfo($m, 'title') . '</a></li>';
    }
}
?>

    </ul>
  </li>
</ul>

<script>
if ($('#appTestToolbar li.dropdown ul.dropdown-menu li').length === 0) {
  $('#appTestToolbar li.dropdown').hide();
}

$(function() {
  var active = '<?= ($OSCOM_Test->getConfigModuleInfo($current_module, 'is_installed') === true) ? $current_module : 'new'; ?>';

  if (active !== 'new') {
    $('#appTestToolbar li[data-module="' + active + '"]').addClass('active');
  } else {
    $('#appTestToolbar li.dropdown').addClass('active');
  }
});
</script>

<?php
if ($OSCOM_Test_Config->is_installed === true) {
    foreach ($OSCOM_Test_Config->req_notes as $rn) {
        echo '<div class="alert alert-warning"><p>' . $rn . '</p></div>';
    }
?>

<form name="paypalConfigure" action="<?= $OSCOM_Test->link('Configure&Process&module=' . $current_module); ?>" method="post">

<div class="panel panel-info oscom-panel">
  <div class="panel-heading">
    <?= $OSCOM_Test->getConfigModuleInfo($current_module, 'title'); ?>
  </div>

  <div class="panel-body">
    <div class="container-fluid">

<?php
    foreach ($OSCOM_Test_Config->getInputParameters() as $cfg) {
        echo $cfg;
    }
?>

    </div>
  </div>
</div>

<p>

<?php
    echo HTML::button($OSCOM_Test->getDef('button_save'), null, null, null, 'btn-success');

    if ($OSCOM_Test->getConfigModuleInfo($current_module, 'is_uninstallable') === true) {
        echo '<span class="pull-right">' . HTML::button($OSCOM_Test->getDef('button_dialog_uninstall'), null, '#', ['params' => 'data-toggle="modal" data-target="#ppUninstallModal"'], 'btn-warning') . '</span>';
    }
?>

</p>

</form>

<?php
    if ($OSCOM_Test->getConfigModuleInfo($current_module, 'is_uninstallable') === true) {
?>

<div id="ppUninstallModal" class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
        <h4 class="modal-title"><?= $OSCOM_Test->getDef('dialog_uninstall_title'); ?></h4>
      </div>
      <div class="modal-body">
        <?= $OSCOM_Test->getDef('dialog_uninstall_body'); ?>
      </div>
      <div class="modal-footer">
        <?= HTML::button($OSCOM_Test->getDef('button_uninstall'), null, $OSCOM_Test->link('Configure&Uninstall&module=' . $current_module), null, 'btn-danger'); ?>
        <?= HTML::button($OSCOM_Test->getDef('button_cancel'), null, '#', ['params' => 'data-dismiss="modal"'], 'btn-link'); ?>
      </div>
    </div>
  </div>
</div>

<?php
    }
} else {
?>

<div class="panel panel-warning">
  <div class="panel-heading">
    <?= $OSCOM_Test->getConfigModuleInfo($current_module, 'title'); ?>
  </div>

  <div class="panel-body">
    <?= $OSCOM_Test->getConfigModuleInfo($current_module, 'introduction'); ?>
  </div>
</div>

<p>
  <?= HTML::button($OSCOM_Test->getDef('button_install_title', ['title' => $OSCOM_Test->getConfigModuleInfo($current_module, 'title')]), null, $OSCOM_Test->link('Configure&Install&module=' . $current_module), null, 'btn-warning'); ?>
</p>

<?php
}

require(__DIR__ . '/template_bottom.php');
?>

We've introduced a couple of extra functions in there which will need to be part of the main app, so open OSC/Apps/Test/Test/Test.php and add the following before the closing } bracket:
 

    public function getConfigModules()
    {
        static $result;

        if (!isset($result)) {
            $result = [];

            $directory = OSCOM::BASE_DIR . 'Apps/Test/Test/Module/Admin/Config';

            if ($dir = new \DirectoryIterator($directory)) {
                foreach ($dir as $file) {
                    if (!$file->isDot() && $file->isDir() && is_file($file->getPathname() . '/' . $file->getFilename() . '.php')) {
                        $class = 'OSC\Apps\Test\Test\Module\Admin\Config\\' . $file->getFilename() . '\\' . $file->getFilename();

                        if (is_subclass_of($class, 'OSC\Apps\Test\Test\Module\Admin\Config\ConfigAbstract')) {
                            $sort_order = $this->getConfigModuleInfo($file->getFilename(), 'sort_order');

                            if ($sort_order > 0) {
                                $counter = $sort_order;
                            } else {
                                $counter = count($result);
                            }

                            while (true) {
                                if (isset($result[$counter])) {
                                    $counter++;

                                    continue;
                                }

                                $result[$counter] = $file->getFilename();

                                break;
                            }
                        } else {
                            trigger_error('OSC\Apps\Test\Test\Test::getConfigModules(): OSC\Apps\Test\Test\Module\Admin\Config\\' . $file->getFilename() . '\\' . $file->getFilename() . ' is not a subclass of OSC\Apps\Test\Test\Module\Admin\Config\ConfigAbstract and cannot be loaded.');
                        }
                    }
                }

                ksort($result, SORT_NUMERIC);
            }
        }

        return $result;
    }
    
    public function getConfigModuleInfo($module, $info)
    {
        if (!Registry::exists('TestAdminConfig' . $module)) {
            $class = 'OSC\Apps\Test\Test\Module\Admin\Config\\' . $module . '\\' . $module;

            Registry::set('TestAdminConfig' . $module, new $class);
        }

        return Registry::get('TestAdminConfig' . $module)->$info;
    }


Now change the action of the button in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates/main.php from
 

'button_install' => HTML::button($OSCOM_Test->getDef('button_install'), null, $OSCOM_Test->link(''), null, 'btn-primary')

to
 

'button_install' => HTML::button($OSCOM_Test->getDef('button_install'), null, $OSCOM_Test->link('Start&Process'), null, 'btn-primary')

To install modules, we need to add a new file in the Configure actions folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Configure

Create a new file in this folder called Install.php
 

<?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\Sites\Admin\Pages\Home\Actions\Configure;

use OSC\OM\Registry;

class Install extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        $current_module = $this->page->data['current_module'];

        $m = Registry::get('TestAdminConfig' . $current_module);
        $m->install();

        $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_module_install_success'), 'success', 'Test');

        $OSCOM_Test->redirect('Configure&module=' . $current_module);
    }
}


Now we need to organise the configuration classes so that it will configure any modules the app may have.

Create a new folder OSC/Apps/Test/Test/Module/Admin/Config and create two new files, ConfigAbstract.php and ConfigParamAbstract.php

In the ConfigAbstract.php file, add 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;

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

abstract class ConfigAbstract
{
    protected $app;

    public $code;
    public $title;
    public $short_title;
    public $introduction;
    public $req_notes = [];
    public $is_installed = false;
    public $is_uninstallable = false;
    public $is_migratable = false;
    public $sort_order = 0;

    abstract protected function init();

    final public function __construct()
    {
        $this->app = Registry::get('Test');

        $this->code = (new \ReflectionClass($this))->getShortName();

        $this->app->loadDefinitions('modules/' . $this->code . '/' . $this->code);

        $this->init();
    }

    public function install()
    {
        $cut_length = strlen('OSCOM_APP_TEST_' . $this->code . '_');

        foreach ($this->getParameters() as $key) {
            $p = strtolower(substr($key, $cut_length));

            $class = 'OSC\Apps\Test\Test\Module\Admin\Config\\' . $this->code . '\Params\\' . $p;

            $cfg = new $class($this->code);

            $this->app->saveCfgParam($key, $cfg->default, isset($cfg->title) ? $cfg->title : null, isset($cfg->description) ? $cfg->description : null, isset($cfg->set_func) ? $cfg->set_func : null);
        }
    }

    public function uninstall()
    {
        $Qdelete = $this->app->db->prepare('delete from :table_configuration where configuration_key like :configuration_key');
        $Qdelete->bindValue(':configuration_key', 'OSCOM_APP_TEST_' . $this->code . '_%');
        $Qdelete->execute();

        return $Qdelete->rowCount();
    }

    public function getParameters()
    {
        $result = [];

        $directory = OSCOM::BASE_DIR . 'Apps/Test/Test/Module/Admin/Config/' . $this->code . '/Params';

        if ($dir = new \DirectoryIterator($directory)) {
            foreach ($dir as $file) {
                if (!$file->isDot() && !$file->isDir() && ($file->getExtension() == 'php')) {
                    $class = 'OSC\Apps\Test\Test\Module\Admin\Config\\' . $this->code . '\\Params\\' . $file->getBasename('.php');

                    if (is_subclass_of($class, 'OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract')) {
                        if ($this->code == 'G') {
                            $result[] = 'OSCOM_APP_TEST_' . strtoupper($file->getBasename('.php'));
                        } else {
                            $result[] = 'OSCOM_APP_TEST_' . $this->code . '_' . strtoupper($file->getBasename('.php'));
                        }
                    } else {
                        trigger_error('OSC\Apps\Test\Test\Module\Admin\Config\\ConfigAbstract::getParameters(): OSC\Apps\Test\Test\Module\Admin\Config\\' . $this->code . '\\Params\\' . $file->getBasename('.php') . ' is not a subclass of OSC\Apps\Test\Test\Module\Admin\Config\ConfigParamAbstract and cannot be loaded.');
                    }
                }
            }
        }

        return $result;
    }

    public function getInputParameters()
    {
        $result = [];

        if ($this->code == 'G') {
            $cut = 'OSCOM_APP_TEST_';
        } else {
            $cut = 'OSCOM_APP_TEST_' . $this->code . '_';
        }

        $cut_length = strlen($cut);

        foreach ($this->getParameters() as $key) {
            $p = strtolower(substr($key, $cut_length));

            $class = 'OSC\Apps\Test\Test\Module\Admin\Config\\' . $this->code . '\Params\\' . $p;

            $cfg = new $class($this->code);

            if (!defined($key)) {
              $this->app->saveCfgParam($key, $cfg->default, isset($cfg->title) ? $cfg->title : null, isset($cfg->description) ? $cfg->description : null, isset($cfg->set_func) ? $cfg->set_func : null);
            }

            if ($cfg->app_configured !== false) {
                if (is_numeric($cfg->sort_order)) {
                    $counter = (int)$cfg->sort_order;
                } else {
                    $counter = count($result);
                }

                while (true) {
                    if (isset($result[$counter])) {
                        $counter++;

                        continue;
                    }

                    $set_field = $cfg->getSetField();

                    if (!empty($set_field)) {
                        $result[$counter] = $set_field;
                    }

                    break;
                }
            }
        }

        ksort($result, SORT_NUMERIC);

        return $result;
    }
}

In the ConfigParamAbstract.php file, add 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;

use OSC\OM\Registry;

abstract class ConfigParamAbstract extends \OSC\Sites\Admin\ConfigParamAbstract
{
    protected $app;
    protected $config_module;

    protected $key_prefix = 'oscom_app_Test_';
    public $app_configured = true;

    public function __construct($config_module)
    {
        $this->app = Registry::get('Test');

        if ($config_module != 'G') {
            $this->key_prefix .= strtolower($config_module) . '_';
        }

        $this->config_module = $config_module;

        $this->code = (new \ReflectionClass($this))->getShortName();

        $this->app->loadDefinitions('modules/' . $config_module . '/Params/' . $this->code);

        parent::__construct();
    }
}


Now create a new folder (you need one for each of your modules if you have more than one) so that your module can be configured -- in this case OSC/Apps/Test/Test/Module/Admin/Config/APPTEST
and in that folder create a file APPTEST.php
 

<?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;

use OSC\OM\OSCOM;

class APPTEST extends \OSC\Apps\Test\Test\Module\Admin\Config\ConfigAbstract
{
    protected $_cm_code = 'product_info/cm_pi_related_products';

    public $is_uninstallable = true;
    public $is_migratable = true;
    public $sort_order = 1000;

    protected function init()
    {
        $this->title = $this->app->getDef('module_apptest_title');
        $this->short_title = $this->app->getDef('module_apptest_short_title');
        $this->introduction = $this->app->getDef('module_apptest_introduction');

        $this->is_installed = defined('OSCOM_APP_TEST_APPTEST_STATUS') && (trim(OSCOM_APP_TEST_APPTEST_STATUS) != '');

    }

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

        $installed = explode(';', MODULE_CONTENT_INSTALLED);
        $installed[] = 'product_info/' . $this->app->vendor . '\\' . $this->app->code . '\\' . $this->code;

        $this->app->saveCfgParam('MODULE_CONTENT_INSTALLED', implode(';', $installed));
    }

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

        $installed = explode(';', MODULE_CONTENT_INSTALLED);
        $installed_pos = array_search('product_info/' . $this->app->vendor . '\\' . $this->app->code . '\\' . $this->code, $installed);

        if ($installed_pos !== false) {
            unset($installed[$installed_pos]);

            $this->app->saveCfgParam('MODULE_CONTENT_INSTALLED', implode(';', $installed));
        }
    }
}


Very importantly, we need to add parameters. You may recognise these from the old modules, something like
 

tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Enable Account Footer Module', 'MODULE_CONTENT_FOOTER_ACCOUNT_STATUS', 'True', 'Do you want to enable the Account content module?', '6', '1', 'tep_cfg_select_option(array(\'True\', \'False\'), ', now())");

 


For apps, things are completely different. There is a special Params folder with one file per parameter for each module.
Right now I'll just make one parameter, status, for our content module, that's just to turn it on or off. We will add new parameters later.

Create a new directory OSC/Apps/Test/Test/Module/Admin/Config/APPTEST/Params and then create a new file in there called status.php

(Note: The class name has to be identical to the filename)

Here are the contents:
 

<?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 = 100;

    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;
    }
}


You will also need to create a corresponding language definition text file for each parameter in OSC/Apps/Test/Test/languages/english/modules/APPTEST/Params, in this case status.txt
 

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


We don't want the Install option to be available on the app menu once we have actually installed the app, so let's make it change to Configure so we can configure the module/s

Open OSC/Apps/Test/Test/Module/Admin/Menu/Test.php and change

 

$test_menu = [
            [
                'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                'title' => $OSCOM_Test->getDef('module_admin_menu_install'),
                'link' => $OSCOM_Test->link()
            ]
        ];

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);

                    
to

     

  $test_menu_check = [
            'OSCOM_APP_TEST_TEST_STATUS'
        ];

        foreach ($test_menu_check as $value) {
            if (defined($value) && !empty(constant($value))) {
                $test_menu = [
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_configure'),
                        'link' => $OSCOM_Test->link('Configure')
                    ]
                ];

                break;
            }
        }

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);

                    
And add
 

module_admin_menu_configure = Configure


to OSC/Apps/Test/Test/languages/english/admin/modules/boxes/test.txt

 

Nearly there!

I've modified the content module file so that it takes advantage of a few features, so open up OSC/Apps/Test/Test/Module/Content/APPTEST.php we created in the first post 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 = 'Test App';
      $this->description = '<div align="center">' . HTML::button($this->app->getDef('module_login_legacy_admin_app_button'), null, $this->app->link('Configure&module=APPTEST'), null, 'btn-primary') . '</div>';
      $this->sort_order = '10';
      
          if ( OSCOM_APP_TEST_APPTEST_STATUS < '1' ) {

            $this->enabled = false;
          } else {
            $this->enabled = true;
          }
    }

    function execute() {
      global $oscTemplate;


      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() {
      return array('OSCOM_APP_TEST_APPTEST_STATUS');
    }
  }
?>


Also, we need to be able to uninstall modules so create a new file in the Configure actions folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Configure called (surprise!) Uninstall.php
 

<?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\Sites\Admin\Pages\Home\Actions\Configure;

use OSC\OM\Registry;

class Uninstall extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        $current_module = $this->page->data['current_module'];

        $m = Registry::get('TestAdminConfig' . $current_module);
        $m->uninstall();

        $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_module_uninstall_success'), 'success', 'Test');

        $OSCOM_Test->redirect('Configure&module=' . $current_module);
    }
}

A couple more language definitions and we're done for the day.

Create a new file configure.txt in OSC/Apps/Test/Test/languages/english/admin
 

section_general = General
section_more = +

dialog_uninstall_title = Uninstall Module?
dialog_uninstall_body = Are you sure you want to uninstall this module?

alert_module_install_success = Module has been successfully installed.
alert_module_uninstall_success = Module has been successfully uninstalled.
alert_cfg_saved_success = Configuration parameters have been successfully saved.


Now you have a functioning app that consists of a static content module. All you can do is turn the module on and off, but the groundwork has been laid for the rest of the app to come to life. You can also use this simple app as a base to experiment and create your own. Next I'll be adding the ability to uninstall the app.

 

Also, once you've installed the content module and it's enabled, then go to Legacy -> Modules -> Content and select Test App, you'll see a Manage App button just like the Paypal App's modules.
 

Share this post


Link to post
Share on other sites

This section is incorrect

 

Open OSC/Apps/Test/Test/Module/Admin/Menu/Test.php and change

 

$test_menu = [
            [
                'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                'title' => $OSCOM_Test->getDef('module_admin_menu_install'),
                'link' => $OSCOM_Test->link()
            ]
        ];

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);
                    

to

     

 

$test_menu_check = [
            'OSCOM_APP_TEST_TEST_STATUS'
        ];

        foreach ($test_menu_check as $value) {
            if (defined($value) && !empty(constant($value))) {
                $test_menu = [
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_configure'),
                        'link' => $OSCOM_Test->link('Configure')
                    ]
                ];

                break;
            }
        }

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);

SHOULD BE:

 

Open OSC/Apps/Test/Test/Module/Admin/Menu/Test.php and change

$test_menu = [
            [
                'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                'title' => $OSCOM_Test->getDef('module_admin_menu_install'),
                'link' => $OSCOM_Test->link()
            ]
        ];

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);

                    
to

     $test_menu = [            [
                'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                'title' => $OSCOM_Test->getDef('module_admin_menu_install'),
                'link' => $OSCOM_Test->link()
            ]
        ]; 

        $test_menu_check = [
            'OSCOM_APP_TEST_TEST_STATUS'
        ];

        foreach ($test_menu_check as $value) {
            if (defined($value) && !empty(constant($value))) {
                $test_menu = [
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_configure'),
                        'link' => $OSCOM_Test->link('Configure')
                    ]
                ];

                break;
            }
        }

        return array('heading' => $OSCOM_Test->getDef('module_admin_menu_title'),
                     'apps' => $test_menu);

Sorry about that!

Share this post


Link to post
Share on other sites

Topic Pinned.

 

I am following this closely, thank you @@frankl

Share this post


Link to post
Share on other sites

@@burt

 

Thanks Gary. Hopefully some people find it useful, I find the concept of Apps really exciting. Guess that makes me an osCommerce Nerd lol.

crimble crumble likes this

Share this post


Link to post
Share on other sites

Oops, another error!

 

in OSC/Apps/Test/Test/Module/Content/APPTEST.php change this line 

$this->description = '<div align="center">' . HTML::button($this->app->getDef('module_login_legacy_admin_app_button'), null, $this->app->link('Configure&module=APPTEST'), null, 'btn-primary') . '</div>';

to

$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>';

Forgot to update the language def

      

Share this post


Link to post
Share on other sites

I'll be posting more of my tutorial soon, stay posted!

beerbee likes this

Share this post


Link to post
Share on other sites

Back to it. I hope it hasn't been too confusing so far.

I've changed my mind about doing the uninstall next, instead I'm going to show what to do with the admin page.

First of all, we need to create the table and some configuration options which, if you've been following along and creating the app with me, requires a reinstall. Luckily, this is easy as.

Use phpMyAdmin or similar to delete any items with OSCOM_APP_TEST_ from the configuration table.

That's it.

 

Before we can reinstall we need to modify and add some files.

Let's add the required items to the install routine by opening OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Start/Process.php and replacing 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\Sites\Admin\Pages\Home\Actions\Start;

use OSC\OM\HTTP;
use OSC\OM\OSCOM;
use OSC\OM\Registry;

class Process extends \OSC\OM\PagesActionsAbstract
{
    
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        if (!defined('OSCOM_APP_TEST_TEST_STATUS')) {
        $OSCOM_Db = Registry::get('Db');
        $OSCOM_Db->save('configuration', [
        'configuration_title' => 'Enable Related Products App',
        'configuration_key' => 'OSCOM_APP_TEST_TEST_STATUS',
        'configuration_value' => 'True',
        'configuration_description' => 'Should we install optional_related_products ?',
        'configuration_group_id' => '6',
        'sort_order' => '2',
        'set_function' => 'tep_cfg_select_option(array(\'True\', \'False\'), ',
        'date_added' => 'now()'
      ]);
        }
        
        $data = [
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL'  => 'True',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME' => 'True',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR' => ' | ',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_ROWS_LIST_OPTIONS' => '20',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH' => '30',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH' => '30',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE' => 'True',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT' => 'False'
            ];

        foreach ($data as $key => $value) {
            $OSCOM_Test->saveCfgParam($key, $value);
        }
        
        $Qcheck = $OSCOM_Db->query('show tables like ":table_products_related_products"');

        if ($Qcheck->fetch() === false) {
            $sql = <<<EOD
CREATE TABLE :table_products_related_products (
  pop_id int(11) NOT NULL auto_increment,
  pop_products_id_master int(11) NOT NULL DEFAULT '0',
  pop_products_id_slave int(11) NOT NULL DEFAULT '0',
  pop_order_id smallint(6) NOT NULL DEFAULT '0',
  PRIMARY KEY (pop_id)
) CHARACTER SET utf8 COLLATE utf8_unicode_ci;
EOD;

            $OSCOM_Db->exec($sql);
        }

        $OSCOM_Test->redirect('Configure');
    }
}

As you can see we've added a database table and some admin options in there so they can be created when we install the app.

We will also need a way to configure those options, so first let's build that page.

Create a new file in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions called Options.php

Put this in it:
 

<?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\Sites\Admin\Pages\Home\Actions;

use OSC\OM\Registry;

class Options extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_Test = Registry::get('Test');

        $this->page->setFile('options.php');
        $this->page->data['action'] = 'Options';

        $OSCOM_Test->loadDefinitions('admin/options');

        $data = [
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_ROWS_LIST_OPTIONS',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE',
            'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT'
        ];

        foreach ($data as $key) {
            if (!defined($key)) {
                $OSCOM_Test->saveCfgParam($key, '');
            }
        }
    }
}


Now create a corresponding folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Options, and create a new file in that folder called Process.php

This file will process any changes we make to the options.

<?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\Sites\Admin\Pages\Home\Actions\Options;

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

class Process extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');

        $data = [];

             $data = [
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL'  => isset($_POST['use_model']) ? HTML::sanitize($_POST['use_model']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME' => isset($_POST['use_name']) ? HTML::sanitize($_POST['use_name']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR' => isset($_POST['model_separator']) ? HTML::sanitize($_POST['model_separator']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_ROWS_LIST_OPTIONS' => isset($_POST['max_rows_list_options']) ? HTML::sanitize($_POST['max_rows_list_options']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH' => isset($_POST['max_name_length']) ? HTML::sanitize($_POST['max_name_length']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH' => isset($_POST['max_display_length']) ? HTML::sanitize($_POST['max_display_length']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE' => isset($_POST['confirm_delete']) ? HTML::sanitize($_POST['confirm_delete']) : '',
                'OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT' => isset($_POST['insert_and_inherit']) ? HTML::sanitize($_POST['insert_and_inherit']) : ''
            ];

        foreach ($data as $key => $value) {
            $OSCOM_Test->saveCfgParam($key, $value);
        }

        $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_credentials_saved_success'), 'success', 'Test');

        $OSCOM_Test->redirect('Admin');
    }
}


Then create the template file, options.php, in the OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates folder.

That file's contents are:
 

<?php
use OSC\OM\HTML;
use OSC\OM\Registry;

$OSCOM_Page = Registry::get('Site')->getPage();
require(__DIR__ . '/template_top.php');
?>

<form name="TestOptions" action="<?= $OSCOM_Test->link('Options&Process'); ?>" method="post">

<div class="panel panel-warning">
  <div class="panel-heading">
    <?= $OSCOM_Test->getDef('app_test_options_title'); ?>
  </div>

  <div class="panel-body">
    <div class="row">
      <div class="col-sm-6">
        <div class="btn-group" data-toggle="buttons">
          <label for="use_model"><?= $OSCOM_Test->getDef('app_test_options_use_model'); ?></label><br>
          <?php echo '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True' ? ' active' : '') . '">' . HTML::radioField('use_model', 'True', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True')) . $OSCOM_Test->getDef('app_test_options_true') . '</label>' .
          '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'False' ? ' active' : '') . '">' . HTML::radioField('use_model', 'False', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'False')) . $OSCOM_Test->getDef('app_test_options_false') . '</label>';
          ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_use_model_desc'); ?></div>

        <div class="btn-group" data-toggle="buttons">
          <label for="use_name"><?= $OSCOM_Test->getDef('app_test_options_use_name'); ?></label><br>
          <?php echo '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True' ? ' active' : '') . '">' . HTML::radioField('use_name', 'True', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True')) . $OSCOM_Test->getDef('app_test_options_true') . '</label>' .
          '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'False' ? ' active' : '') . '">' . HTML::radioField('use_name', 'False', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'False')) . $OSCOM_Test->getDef('app_test_options_false') . '</label>';
          ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_use_name_desc'); ?></div>

        <div class="form-group">
          <label for="model_separator"><?= $OSCOM_Test->getDef('app_test_options_model_separator'); ?></label>
          <?php echo HTML::inputField('model_separator', OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR, 'id="model_separator"'); ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_model_separator_desc'); ?></div>
        
        <div class="btn-group" data-toggle="buttons">
          <label for="use_model"><?= $OSCOM_Test->getDef('app_test_options_confirm_delete'); ?></label><br>
          <?php echo '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE == 'True' ? ' active' : '') . '">' . HTML::radioField('confirm_delete', 'True', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE == 'True')) . $OSCOM_Test->getDef('app_test_options_true') . '</label>' .
          '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE == 'False' ? ' active' : '') . '">' . HTML::radioField('confirm_delete', 'False', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_CONFIRM_DELETE == 'false')) . $OSCOM_Test->getDef('app_test_options_false') . '</label>';
          ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_confirm_delete_desc'); ?></div>
      </div>

      <div class="col-sm-6">
        <div class="form-group">
          <label for="max_rows_list_options"><?= $OSCOM_Test->getDef('app_test_options_max_rows_list_options'); ?></label>
          <?php echo HTML::inputField('max_rows_list_options', OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_ROWS_LIST_OPTIONS, 'id="max_rows_list_options"'); ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_max_rows_list_options_desc'); ?></div>

        <div class="form-group">
          <label for="max_name_length"><?= $OSCOM_Test->getDef('app_test_options_max_name_length'); ?></label>
          <?php echo HTML::inputField('max_name_length', OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH, 'id="max_name_length"'); ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_max_name_length_desc'); ?></div>

        <div class="form-group">
          <label for="max_display_length"><?= $OSCOM_Test->getDef('app_test_options_max_display_length'); ?></label>
          <?php echo HTML::inputField('max_display_length', OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH, 'id="max_display_length"'); ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_max_display_length_desc'); ?></div>
        <div class="btn-group" data-toggle="buttons">
          <label for="insert_and_inherit"><?= $OSCOM_Test->getDef('app_test_options_insert_and_inherit'); ?></label><br>
          <?php echo '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT == 'True' ? ' active' : '') . '">' . HTML::radioField('insert_and_inherit', 'True', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT == 'True')) . $OSCOM_Test->getDef('app_test_options_true') . '</label>' .
          '  <label class="btn btn-info' . (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT == 'False' ? ' active' : '') . '">' . HTML::radioField('insert_and_inherit', 'False', (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_INSERT_AND_INHERIT == 'False')) . $OSCOM_Test->getDef('app_test_options_false') . '</label>';
          ?>
        </div>
        <div class="help-block"><?= $OSCOM_Test->getDef('app_test_options_insert_and_inherit_desc'); ?></div>
      </div>
    </div>
  </div>
</div>

<p><?= HTML::button($OSCOM_Test->getDef('button_save'), null, null, null, 'btn-success'); ?></p>

</form>

<?php
require(__DIR__ . '/template_bottom.php');
?>


And lastly, we need the language definitions file in OSC/Apps/Test/Test/languages/english/admin, options.txt

Contents:
 

app_test_options_title = Related Products Admin Options
app_test_options_true = True
app_test_options_false = False
app_test_options_use_model = Use Model?
app_test_options_use_model_desc = Use product model in lists. When product name or Id is also selected, product ID is displayed first.
app_test_options_use_name = Use Name?
app_test_options_use_name_desc = Use product name in lists. When product model or ID is also selected, product ID or model is displayed first.
app_test_options_model_separator = Separator
app_test_options_model_separator_desc = Enter the characters you would like to separate ID, model and name, when using 2 or 3. Leave empty if only using one.
app_test_options_confirm_delete = Confirm delete?
app_test_options_confirm_delete_desc = When set to True, a confirmation box will pop-up when deleting an association. Set to False to delete without confirmation.
app_test_options_insert_and_inherit = Combine Insert with Inherit
app_test_options_insert_and_inherit_desc = When set to True, clicking on Inherit will also Insert the product association. When False, Inherit works as before.
app_test_options_max_rows_list_options = Maximum Rows
app_test_options_max_rows_list_options_desc = Sets the maximum number of rows to display per page.
app_test_options_max_name_length = Drop-Down List Maximum Length
app_test_options_max_name_length_desc = Sets the maximum length (in characters) of product name displayed in drop-down lists. Enter 0 to set this option to false.
app_test_options_max_display_length = Display List Maximum Length
app_test_options_max_display_length_desc = Sets the maximum length (in characters) of product name displayed in list. Enter 0 to set this option to false.
alert_credentials_saved_success = Configuration options saved


The Options page is now complete.

 

We won't do a menu item for the options, we'll have a button from the main admin page (which we are about to create) to reach the options configuration page.

We will need to create a menu item for the main administration page though, so open up OSC/Apps/Test/Test/Module/Admin/Menu/Test.php

Change
       

foreach ($test_menu_check as $value) {
            if (defined($value) && !empty(constant($value))) {
                $test_menu = [
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_configure'),
                        'link' => $OSCOM_Test->link('Configure')
                    ]
                    
                ];

                break;
            }
        }

To

 

     foreach ($test_menu_check as $value) {
            if (defined($value) && !empty(constant($value))) {
                $test_menu = [
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_admin'),
                        'link' => $OSCOM_Test->link('Admin')
                    ],
                    [
                        'code' => $OSCOM_Test->getVendor() . '\\' . $OSCOM_Test->getCode(),
                        'title' => $OSCOM_Test->getDef('module_admin_menu_configure'),
                        'link' => $OSCOM_Test->link('Configure')
                    ]
                    
                ];

                break;
            }
        }

        
We also need the language definition for that menu item, so open up OSC/Apps/Test/Test/languages/english/admin/modules/boxes/test.txt and make sure you have:
 

module_admin_menu_title = Test App
module_admin_menu_install = Install
module_admin_menu_configure = Configure
module_admin_menu_admin = Admin


Now for the admin page itself.

Create a new file in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions called Admin.php

Once again, in the same process as when we created the Options page, we also need to create a corresponding folder in the Action folder containing action file/s; a template; and a language definitions file.

Put the following content in OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Admin.php
 

<?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\Sites\Admin\Pages\Home\Actions;

use OSC\OM\Registry;

class Admin extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_PayPal = Registry::get('Test');

        $this->page->setFile('admin.php');
        $this->page->data['action'] = 'Admin';

        $OSCOM_PayPal->loadDefinitions('admin/admin');
    }
}


Create the new folder OSC/Apps/Test/Test/Sites/Admin/Pages/Home/Actions/Admin and in that folder create 2 files. Why 2? Because we want to take actions on the Admin page that need to be processed using different namespaces.

The first file is Admin.php. Put this in it:
 

<?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\Sites\Admin\Pages\Home\Actions\Admin;

use OSC\OM\Registry;

class Admin extends \OSC\OM\PagesActionsAbstract
{

    
        public function execute()
    {
            
                $this->page->setFile('admin.php');

        }

}


The second file is Insert.php. We are going to use that to insert rows in the related products table. Copy this into it:
 

<?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\Sites\Admin\Pages\Home\Actions\Admin;

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

class Insert extends \OSC\OM\PagesActionsAbstract
{
    public function execute()
    {
        $OSCOM_MessageStack = Registry::get('MessageStack');
        $OSCOM_Test = Registry::get('Test');
        $OSCOM_Db = Registry::get('Db');


                $products_id_master = isset($_POST['products_id_master']) ? HTML::sanitize($_POST['products_id_master']) : '';
                $products_id_slave = isset($_POST['products_id_slave']) ? HTML::sanitize($_POST['products_id_slave']) : '';
                $pop_order_id = isset($_POST['pop_order_id']) ? HTML::sanitize($_POST['pop_order_id']) : '';


        if ($products_id_master != $products_id_slave) {
            
            $Qcheck = $OSCOM_Db->prepare('select pop_id from :table_products_related_products where pop_products_id_master = :products_id_master and pop_products_id_slave = :products_id_slave');
            $Qcheck->bindInt(':products_id_master', $products_id_master);
            $Qcheck->bindInt(':products_id_slave', $products_id_slave);
            $Qcheck->execute();
            if ($Qcheck->fetch() === false) {
              $Qinsert = $OSCOM_Db->prepare('insert into :table_products_related_products (pop_products_id_master, pop_products_id_slave, pop_order_id) values (:products_id_master, :products_id_slave, :pop_order_id)');
              $Qinsert->bindInt(':products_id_master', $products_id_master);
              $Qinsert->bindInt(':products_id_slave', $products_id_slave);
              $Qinsert->bindInt(':pop_order_id', $pop_order_id);
              $Qinsert->execute();

                 $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_product_added_success'), 'success', 'Test');
            } else {
                $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_product_added_failure'), 'warning', 'Test');
            }
        }

        $OSCOM_Test->redirect('Admin&products_id_master=' . $products_id_master);
    }
}


Of course we need the template file (in this case OSC/Apps/Test/Test/Sites/Admin/Pages/Home/templates/admin.php)

Put this in it:
 

<?php

  use OSC\OM\FileSystem;
  use OSC\OM\HTML;
  use OSC\OM\OSCOM;
  use OSC\OM\Registry;
 
  $OSCOM_Page = Registry::get('Site')->getPage();

  require(__DIR__ . '/template_top.php');

  if (!isset($_GET['page']) || !is_numeric($_GET['page'])) {
    $_GET['page'] = 1;
  }

  $action = (isset($_GET['action']) ? $_GET['action'] : '');
 
  $products_id_slave = '';
  $products_id_master = '';
  $products_id_view = '';
 
  $products_id_view = (isset($_GET['products_id_view']) && $_GET['products_id_view'] != 0) ? (int)$_GET['products_id_view'] : null;
  $products_id_master = (isset($_GET['products_id_master']) && $_GET['products_id_master'] != 0) ? (int)$_GET['products_id_master'] : null;
  if ($products_id_master) {
    $products_id_view = $products_id_master;
  }
 
  if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True' && OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
        $MenuOrderBy = ' order by pd.products_name, p.products_model';
  } elseif (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
        $MenuOrderBy = ' order by pd.products_name';
  } elseif (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') {
        $MenuOrderBy = ' order by p.products_model';
  }
 

?>
<div class="text-right">
  <?= HTML::button($OSCOM_Test->getDef('app_test_button_options'), null, $OSCOM_Test->link('Options'), null, 'btn-info'); ?>
</div>
<h2><i class="fa fa-arrows-h"></i> <a href="<?= $OSCOM_Test->link('Admin'); ?>"><?= $OSCOM_Test->getDef('app_test_admin_heading_title'); ?></a></h2>

<?php
    if ( defined('OSCOM_APP_TEST_TEST_STATUS') )
    { // check if product info module installed
?>
<div class="relatedShowAll form-inline col-sm-6">
<?php
            $related_what = "p.products_id";                
              $related_from =  " from :table_products p, :table_products_related_products pa";
            $related_where =  " where pa.pop_products_id_master = p.products_id";
              if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') {
                  $related_what .= ", p.products_model";
              }
              if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
                  $related_what .= ", pd.products_name";
                  $related_from .=  ", :table_products_description pd";
                  $related_where .=  " and p.products_id = pd.products_id";
                  $related_where .=  " and pd.language_id = :languages_id";
            }
            
            $Qrelated = $OSCOM_Test->db->prepare('select distinct ' . $related_what . $related_from . $related_where . $MenuOrderBy);
            $Qrelated->bindInt(':languages_id', $OSCOM_Language->getId());
            $Qrelated->execute();
            
            $products_array[] = array('id' => '',
                                        'text' => $OSCOM_Test->getDef('app_test_admin_show_all'));
                                      
            while ($Qrelated->fetch()) {
                $model = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True')? $Qrelated->value('products_model') . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR : '' ;
                  $name = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True')? $Qrelated->value('products_name') : '' ;
                  $products_array[] = array('id' => $Qrelated->value('products_id'),
                                      'text' => $model . $name );
        }
        
        echo '<label for="products_id_master">Select Product:</label>' .
                    HTML::selectField('products_id_view', $products_array, (isset($_GET['products_id_view']) ? $_GET['products_id_view'] : ''), 'onchange="document.location.href=\'' . $OSCOM_Test->link("Admin&products_id_view='+this.value+'", "&products_id_view='+this.value+'").'\'"');
?>
</div>
<table class="oscom-table table table-hover">
  <thead>
    <tr class="info">
      <th class="text-left"><?php echo $OSCOM_Test->getDef('app_test_admin_table_heading_rel_id'); ?></th>
      <th class="text-left"><?php echo ((OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') ? $OSCOM_Test->getDef('app_test_admin_table_heading_model') . ' ' . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR . ' ' : '') . ((OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') ? $OSCOM_Test->getDef('app_test_admin_table_heading_product') : '') . ' ' . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR . ' ' . $OSCOM_Test->getDef('app_test_admin_table_heading_related'); ?></th>
      <th class="text-left"><?php echo ((OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') ? $OSCOM_Test->getDef('app_test_admin_table_heading_model') . ' ' . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR . ' ' : '') . ((OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') ? $OSCOM_Test->getDef('app_test_admin_table_heading_product') : ''); ?></th>
      <th class="text-left"><?php echo $OSCOM_Test->getDef('app_test_admin_table_heading_order'); ?></th>
      <th class="action"><?php echo $OSCOM_Test->getDef('app_test_admin_table_heading_action'); ?></th>
    </tr>
  </thead>
  <tbody>
 
    <?php
    $related_query = "select SQL_CALC_FOUND_ROWS pa.*, pd.products_id, p.products_model
                        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";


         if ($products_id_view) {
              $related_query .= " where pd.products_id = '$products_id_view'";
        }
              $related_query .= "$MenuOrderBy, pa.pop_order_id, pa.pop_id";
            $related_query .= " limit :page_set_offset, :page_set_max_results";
    $Qrelated = $OSCOM_Db->prepare($related_query);
    $Qrelated->bindInt(':languages_id', $OSCOM_Language->getId());
    $Qrelated->setPageSet('20');
    $Qrelated->execute();
    

    $next_id = 1;

      $rows = 0;
      $mId = null;
      $sId = null;
      $mModel = null;
      $sModel = null;                  
      while ($Qrelated->fetch()) {
          if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') {
            $mModel = $OSCOM_Test->osc_get_products_model($Qrelated->valueInt('pop_products_id_master')) . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR . ' ';
            $sModel = $OSCOM_Test->osc_get_products_model($Qrelated->valueInt('pop_products_id_slave')) . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR . ' ';
          }
          if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
            $products_name_master = tep_get_products_name($Qrelated->valueInt('pop_products_id_master'));
            $products_name_slave = tep_get_products_name($Qrelated->valueInt('pop_products_id_slave'));
          }

          $pop_order_id = $Qrelated->valueInt('pop_order_id');
          $rows++;
    
    ?>
    <tr>
      <td> <?php echo $Qrelated->valueInt("pop_id"); ?> </td>
      <td> <?php echo $mId  ?><?php echo $mModel ?><?php echo (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH== '0')?$products_name_master:substr($products_name_master, 0, OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH); ?> </td>
      <td> <?php echo $sId  ?><?php echo $sModel ?><?php echo (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH== '0')?$products_name_slave:substr($products_name_slave, 0, OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_DISPLAY_LENGTH); ?> </td>
      <td align="center"> <?php echo $pop_order_id; ?> </td>
      <td align="center" class="smallText"></td></tr>
    <?php
    }
    ?>
  </tbody>
</table>

<table class="oscom-table table table-hover">
  <tr>
   <td>
    <form class="form-inline" name="relatedDropdown" action="<?= $OSCOM_Test->link('Admin&Insert'); ?>" method="post">
        <div class="form-group col-sm-4">
          <label for="products_id_master">Select master:</label>
            <select class="form-control" name="products_id_master">                                    
            <?php
                $related_what = "p.products_id";                
                $related_from =  " from :table_products p";
                $related_where =  " where p.products_id > '0'";
                if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') {
                    $related_what .= ", p.products_model";
                }
                if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
                    $related_what .= ", pd.products_name";
                    $related_from .=  ", :table_products_description pd";
                    $related_where .=  " and p.products_id = pd.products_id";
                    $related_where .=  " and pd.language_id = :languages_id";
                }
                
                $related_query_raw = "select distinct " . $related_what . $related_from . $related_where . ' ' . $MenuOrderBy;
                                    
                $Qrelated_select = $OSCOM_Test->db->prepare($related_query_raw);
                $Qrelated_select->bindInt(':languages_id', $OSCOM_Language->getId());
                $Qrelated_select->execute();

                if (!$products_id_master) {
                    $products_id_master = $products_id_view;
                }
                

                while ($Qrelated_select->fetch()) {    
                    $model = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True')?$Qrelated_select->value('products_model') . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR:'';
                    $name = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True')?$Qrelated_select->value('products_name'):'';
                    $product_name = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH == '0')?$name:substr($name, 0, OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH);
                    echo '<option id="c' . $Qrelated_select->value('products_id') . '" value="' . $Qrelated_select->valueInt('products_id') . '"' . (($products_id_master == $Qrelated_select->valueInt('products_id'))? ' selected' : '') . '>' . $model . $product_name . '</option>';
                                            
                }
?>
            </select>
        </div>
        <div class="form-group col-sm-4">
          <label for="products_id_slave">Select slave:</label>
            <select class="form-control" name="products_id_slave">
            <?php
                $related_what = "p.products_id";                
                $related_from =  " from :table_products p";
                $related_where =  " where p.products_id > '0'";
                if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True') {
                    $related_what .= ", p.products_model";
                }
                if (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True') {
                    $related_what .= ", pd.products_name";
                    $related_from .=  ", :table_products_description pd";
                    $related_where .=  " and p.products_id = pd.products_id";
                    $related_where .=  " and pd.language_id = :languages_id";
                }
                
                $related_query_raw = "select distinct " . $related_what . $related_from . $related_where . ' ' . $MenuOrderBy;
                $Qrelated_select = $OSCOM_Test->db->prepare($related_query_raw);
                $Qrelated_select->bindInt(':languages_id', $OSCOM_Language->getId());
                $Qrelated_select->execute();
                                        
                while ($Qrelated_select->fetch()) {
                    $model = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_MODEL == 'True')?$Qrelated_select->value('products_model') . OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MODEL_SEPARATOR:'';
                    $name = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_USE_NAME == 'True')?$Qrelated_select->value('products_name'):'';
                    $product_name = (OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH == '0')?$name:substr($name, 0, OSCOM_APP_TEST_TEST_RELATED_PRODUCTS_MAX_NAME_LENGTH);
                    echo '<option id="d' . $Qrelated_select->value('products_id') . '" value="' . $Qrelated_select->value('products_id') . '"' . (($products_id_slave == $Qrelated_select->value('products_id'))? ' selected' : '') . '>' . $model . $product_name . '</option>';
                }
            ?>
            </select>
          </div>
          <div><?= HTML::button($OSCOM_Test->getDef('button_save'), null, null, null, 'btn-success'); ?></div>
    </form>
   </td>
  </tr>
</table>

<div>
  <span class="pull-right"><?= $Qrelated->getPageSetLinks(); ?></span>
  <?= $Qrelated->getPageSetLabel($OSCOM_Test->getDef('app_text_display_number_of_related')); ?>
</div>


<?php
  } else { //App is not installed
      $OSCOM_MessageStack->add($OSCOM_Test->getDef('alert_app_not_installed'), 'warning', 'Test');
  }

  require(__DIR__ . '/template_bottom.php');
?>

Now we need the language definitions file. Create OSC/Apps/Test/Test/languages/english/admin/admin.txt.

This is the content:

 

app_test_admin_heading_title = Related Products
app_test_admin_table_heading_model = Model
app_test_admin_select_product = Select Product
app_test_admin_show_all = Show All
app_test_admin_table_heading_rel_id = ID
app_test_admin_table_heading_product = Product
app_test_admin_table_heading_related = Related
app_test_admin_table_heading_order = Sort Order
app_test_admin_table_heading_action = Action
app_test_admin_table_heading_master_products = Master Product
app_test_admin_table_heading_slave_products = Slave Product
app_test_admin_button_insert = Insert
app_test_admin_button_reciprocate = Reciprocate
app_test_admin_button_inherit = Inherit
app_test_admin_title_maintenance = Maintenance
app_test_admin_title_maintenance_desc = Delete orphan products from related products table.
app_test_admin_button_maintenance = Delete orphans
app_test_admin_delete = Delete
app_test_admin_edit = Edit
app_test_admin_confirm_delete = Are you sure you wish to delete this item?
app_test_button_options = Options
alert_product_added_success = Added new related product
alert_product_added_failure = These products are already related
app_text_display_number_of_related = Displaying <strong>{{listing_from}}</strong> to <strong>{{listing_to}}</strong> (of <strong>{{listing_total}}</strong> related products)
alert_app_not_installed = This app isn't installed yet.

That's nearly it for today.

Note: This is a very basic version of a Related Products administration page, but it works fine for our testing purposes (plus it makes the file less complicated in case someone uses this Admin section to create a new app). In the future we'll upgrade this admin page to full functionality.

You may notice in the admin.php template file that we are using a new function which is called with this line

$OSCOM_Test->osc_get_products_model


That function goes in the OSC/Apps/Test/Test/Test.php so it is available for use throughout the app.

Simply open Test.php and add before the last curly bracket:

   

public function osc_get_products_model($product_id) {
        
        $Qmodel = $this->db->prepare('select products_model from :table_products where products_id = :products_id');
        $Qmodel->bindInt(':products_id', (int)$product_id);
        $Qmodel->execute();
    return $Qmodel->value('products_model');
    }
    

Now go ahead and install the app by clicking on the Apps->Test App menu and choosing Install (which should be the only option)

(There is no need to install the content module yet, that will be worked on next)

Now go to the Apps->Test App menu again, there should be two menu items - Admin, and Configure.  Choose Admin. If you've followed these instructions correctly you will end up at the administration page where you can create product relationships. Try a few out. At the moment we can only relate one product to another, there is no way to edit or delete these entries but that is something we will work on down the track. At the moment I'm keeping it simple so that this app could be used as a base for your app.

 

You can also click on the Options button to see how you can change the options for the app. Not all of them connect to things yet but most do, so play around.

 

As you are also probably aware, the app is completely self contained. We've added database tables, configuration parameters, pages, functions, a menu, and a content module without touching a single line of core code or having to install SQL manually or even having to install files to various different folders. The possibilities are exciting wouldn't you agree?

 

Next I'll tidy up the content module.

Share this post


Link to post
Share on other sites

Posted (edited)

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

Params.zip

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

LangParams.zip

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> ';
                    $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 .= ' ...';
                }
            }
                
            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
marcello likes this

Share this post


Link to post
Share on other sites

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')));

Share this post


Link to post
Share on other sites

Posted (edited)

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.oscommerce.com/Online&en&oscom_2_4&developers&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/frankludriks/osCommerce-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
raiwa, beerbee, burt and 1 other like this

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now