Jump to content

Abdy

Blesta Developers
  • Posts

    407
  • Joined

  • Last visited

  • Days Won

    36

Posts posted by Abdy

  1. 4 minutes ago, Paul said:

    Can you check your Blesta logs under ../logs_blesta/ to see what the error is? I'm assuming it's a MySQL error, but the text of the error would be helpful.

    It may make sense for us to look into adding emoji support to the ticket system.

    I just tried it and indeed it's a MySQL error. SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\xA4\xA0' for column 'details' at row 1

  2. On 7/27/2020 at 3:31 PM, Anton Qcl said:

    @Abdy, good idea  but in this case client need to do two additional actions every time he wants to use custom search. It is not acceptable. Probably I could've try to go this way if there was an option to change default search. Right now it is 'Smart Search' and I did not find the way to change it. Is it possible?
    However, I like the idea of using events, so I did it.

    Following code monitor event Appcontroller.preAction and fire custom search function if detect that url is /admin/search. If clients are found, function will redirect user to the first client's page.
    It is crutchy but, unfortunately, I did not find the way to get the controller in my function interceptAdminSearch - I could possible use only var $event which does not contain any useful information.

    
    <?php
    
    class AtnSmartsearchPlugin extends Plugin 
    {
        public function __construct() 
        {
            $this->loadConfig(dirname(__FILE__) . DS . "config.json");
        }
        
        public function getEvents()
        {
            return [
                [
                    'event'     => "Appcontroller.preAction",
                    'callback'  => ["this", "interceptAdminSearch"]
                ]
            ];
        }
     
        public function interceptAdminSearch()
        {
            if (
                strpos($_SERVER['REQUEST_URI'], "/admin/search") === 0
                && $_SERVER['REQUEST_METHOD'] === "POST"
            ) {
                $this->searchClients();
            }
        }
        
        public function searchClients()
        {
            $this->requireLogin();
            
            $value = htmlentities(strip_tags($_POST['search']));
            $clients = $this->myFunctionToSearchClients($value);        
            
            if (!empty($clients)) {
                $client = array_pop($clients);                        
                $this->redirect($client->url);
            }
        }
        
        
        
        
        // Just an example, not a real function
        private function myFunctionToSearchClients($value)
        {
            if ($value == "example") {
                return [
                    (object)['url'=>"/admin/clients/view/1/", 'name'=>"Test Client1"],
                    (object)['url'=>"/admin/clients/view/2/", 'name'=>"	TestClient2"],
                ];
            }
        }
    
        private function requireLogin()
        {        
            if (empty($_SESSION['blesta_staff_id'])) {
                $this->redirect("/admin/login");
            }
        }
        
        private function redirect($url)
        {
            header("Location: {$url}");
            die();        
        }
    }


    Technically, this approach solves my problem but I would like to show information about other found clients somehow. It would be ok if I could add it in a flash message, but method flashMessage() is protected and defined in AppController.
    I tried to create custom controller inside my plugin but it did not work - no errors, but no flash messages after using it:
     

    
    <?php
    
    class CustomSearch extends AppController
    {
        public function setFlashMessage($message) 
        {
            $this->flashMessage("message", $message, ['preserve_tags'=>true], false);
        }
    }
    
    <?php
    
    class AtnSmartsearchPlugin extends Plugin 
    {    
        public function searchClients()
        {
            ...
            
            if (!empty($clients)) {
                $client = array_pop($clients);
                
                if (count($clients) > 0) {
                    $this->addClientsInfoToFlashMessages($clients,);
                }        
                            
                ...
            }
        }
    
        
        private function addClientsInfoToFlashMessages($clients,)
        {
            $clientsInfo = array_map(function($client) {
                return "<a href='{$client->url}'>{$client->name}</a>";
            }, $clients);
            $clientsInfo = implode(nl2br(PHP_EOL), $clientsInfo);
            $message = "Also found clients:<br>{$clientsInfo}";
             
            $controller = $this->loadCustomSearchController();  
            $controller->setFlashMessage($message);
        }
        
        private function loadCustomSearchController()
        {
            require __DIR__ . "/controllers/custom_search.php";
            $controller = new \CustomSearch(null, null, false);
            
            return $controller;
        }
     
    }


     

    You can change Smart Search to another one, clicking on the magnifying glass icon.

    408382899_ScreenShot2020-07-31at11_49_40AM.png.3a7bb0b7d560ed52b3e2fc9999a713d2.png

  3. I think the best way to implement your own custom search is by using events and not a route. 

    To implement it using events you need to register the event in the *_plugin.php file as in the following example: In this example we register the search() function from the AdminMain controller.

    class SearchPlugin extends Plugin {
    	
        // ...
    
        public function customSearch($event)
        {
            $params = $event->getParams();
    
            if (isset($params['options'])) {
                $params['options'] += [
                    $params['base_uri'] . 'plugin/search/admin_main/search/' => 'Custom Search'
                ];
            }
    
            $event->setParams($params);
        }
    
        public function getEvents()
        {
            return [
                [
                    'event' => 'Navigation.getSearchOptions',
                    'callback' => ['this', 'customSearch']
                ]
            ];
        }
    }

    Now we need to create the AdminMain controller and implement the search() method, which will perform the search:

    <?php
    class AdminMain extends AppController
    {
        public function preAction()
        {
            parent::preAction();
    
            $this->requireLogin();
    
            // Load the languages, models, components
            // and helpers needed by this controller.
        }
    
        public function index()
        {
            // There is nothing to see here
            $this->redirect($this->base_uri);
        }
    
        public function search()
        {
            // Restore structure view location of the admin portal
            $this->structure->setDefaultView(APPDIR);
            $this->structure->setView(null, $this->orig_structure_view);
    
            // Set page title
            $this->structure->set('page_title', 'My Custom Search');
    
            // Implement your amazing code here to do the search!
    
            if ($this->isAjax()) {
                return $this->renderAjaxWidgetIfAsync(
                    isset($this->post['search']) ? null : isset($this->get['search'])
                );
            }
        }
    }

     

  4. I think it is not necessary to use events for what you are looking for, from the addService() function you can add a condition that if the configurable option exists, a support ticket is created. The addService() function is automatically executed by the cron task when the invoice related to the service has been paid in full.

    Here's a quick example, assuming that the configurable option for the extra service is named "migration_service":

    public function addService(
        $package,
        array $vars = null,
        $parent_package = null,
        $parent_service = null,
        $status = 'pending'
    ) {
        ...
    
        if (isset($vars['configoptions']['migration_service'])) {
            // Create support ticket
            Loader::loadModels($this, ['SupportManager.SupportManagerTickets']);
    
            $ticket = $this->SupportManagerTickets->add([
                'department_id' => 1,
                'staff_id' => 1,
                'client_id' => 1,
                'summary' => 'Subject',
                'priority' => 'critical',
                'status' => 'open'
            ]);
    
            // Add reply
            if (!is_null($ticket)) {
                $this->SupportManagerTickets->addReply($ticket, [
                    'client_id' => 1,
                    'contact_id' => 1,
                    'type' => 'reply',
                    'details' => 'Reply text'
                ]);
            }
        }
    
        ...
    }

     

  5. 26 minutes ago, Richzendy said:

    Hi all,

    How this:

    
    $this->uses(['SupportManager.SupportManagerTickets']);

    Can be used from a module? i can make a button on a client tab just to create a ticket with a format to require something with a pre-defined data.

    Try using

    Loader::loadModels($this, ['SupportManager.SupportManagerTickets']);

    This is due to $this->uses() is not accessible from a module.

  6. You can use add-ons instead of configurable options. Add-ons can have different terms than the main package.

    Here's a quick example, I created an add-on package for a one time cost of $200. This package belongs to a package group of the Add-on type named Additional Services.

    875618986_ScreenShot2020-06-10at9_50_21AM.png.b66f3220a30eb76bf7afb37f858bb02d.png

    I assigned the Hosting package group as a parent of the Additional Services package group.

    722539691_ScreenShot2020-06-10at10_02_47AM.png.362ea513cb4d858fb6ecc91389d91655.png

    At the moment of adding a new service, regardless of the term of the parent package, the add-on "Migration Service" can be added, even though the parent package is monthly and the add-on is one time.

    42950213_ScreenShot2020-06-10at9_57_40AM.png.c3ba5a3c47a7d287c5747e03c4739757.png

  7. This usually happens when some definitions are missing from the language pack in use, Blesta does not come by default with a dropdown called "Billing" in the navigation bar. Maybe this one is being added by a third party plugin?

    If you are using a third party plugin, you may need to update the language files of that plugin. However, you can find the language definitions from the navigation bar on the /language/xx_xx/navigation.php file, where xx_xx is the language pack you are using. 

  8. The navigation menu can be found on /app/views/client/bootstrap/structure.pdt, in this file is available the $logged_in variable, which allows you to know if a user has logged in or not.

    <?php
    if ($this->Html->ifSet($logged_in)) {
    ?>
        <!-- The user is logged in: navmenu1 -->
    <?php
    } else {
    ?>
        <!-- The user is not logged in: navmenu2 -->
    <?php
    }
    ?>

     

  9. 13 hours ago, Paul said:

    You'd have to export the FOUR theme, then import it, and then you can add the logo URL. I see how this is a bit unfriendly, and we can probably improve that.

    Regarding the logo image, if we allow it to be uploaded through Blesta, it would probably still be unique to the theme under Look and Feel. Also, files uploaded through Blesta go into the uploads directory which we recommend not be publicly accessible, so it might be necessary to serve the logo via PHP if we store it in the same way.

    I think the logo image could be encoded in Base64 and saved as a company setting in the database. Probably a very large image could affect performance, so before encoding, it should be resized and perhaps compressed. Although I don't know if it can affect performance in a real-life scenario.

  10. Everyone who requested a refund received it within 24 hours.

    I'm really sorry about what happened and promise to reward the people involved as an apology. Everyone who received a refund, will still receive the license (perk) they originally purchased when the module gets released for free.

    I don't want to make the same mistake again, so this time I'd rather not give a release date. I take responsibility for the mistake I made and I hope to be able to amend my mistake soon and make my best effort to never repeat it again. I don't want to use this as an excuse, I take full responsibility for the mistake I made, but I think I owe everyone an explanation. 

    The development of the module started in the last week of November 2017, but because I had several projects and tasks to do for other clients, I had no more time available in my schedule to develop the module on my own. So I decided to hire a full-time PHP developer in the first week of December to take over the development of the module. However, for reasons unknown to me, the last week of December my developer quit, causing development to stop.  I personally like to make code that not only works, but is also visually beautiful, trying my best to follow the development standards and follow good coding practices. 

    However, I couldn't find another developer that met my criteria, because very few developers are familiar with Blesta. Because of this I started working on the module on my own during the weekends to prevent development from stopping completely, but it still ended up being delayed considerably. Having already finished some of the biggest projects I had, last week I resumed the development of the module again, at half time. Also, last week I got a new developer who will be able to help me in the development.

    During this time I decided to make some changes. The final release of the module will not be called cPanel Extended, as I try to avoid confusion with the ModulesGarden module. In addition, the module will not be released under the CyanDark brand, as I am working on a new site specifically dedicated to sell only Blesta related products. With a development team independent of the CyanDark's team to avoid problems.

    This is one of the most popular modules that I have, and I promise you that I will not let the module die, soon I will publish more news about the progress of development, but I prefer not to give an ETA at the moment, to avoid problems.

    Anyway, I give you my sincere apologies and I hope you can pardon me for my mistake.

×
×
  • Create New...