Prevent publish all in silverstripe - php

Silverstripe has the ability for users to publish all pages on a site available at /admin/pages/publishall and the publish all code is in /cms/code/controllers/CMSMain.php
We've had some problems with users accidentally running publish all in production when it should only be run from other environments - How can I disable the publishall functionality?

My first approach to fixing this was to create a custom CMSPageController.php in which I would overwrite publishall, and just place a call to parent::publishall($request) inside an if block checking which environment we were in. The problem with this approach was that injecting the custom class created issues with editing pages. Ideally I would have been able to just extend publishall but as Nunser mentioned above, Silverstripe(frustratingly) hasn't included extension hooks in that method.
I ended up creating a custom route to overwrite admin/pages/publishall
so inside mysite/_config/routes.yml
'admin/pages/publishall': CustomPublishallController
and in mysite/code/controllers/CustomPublishallController.php
<?php
/**
* Controller for providing silverstripes inbuilt publishall functionality with the ability to
* run in production removed.
*
* #package sitename
* #subpackage mysite
*/
class CustomPublishallController extends Controller {
public function index($request){
$cmsMain = new CMSMain();
if(ENVIRONMENT_CONSTANT != 'prod'){
return $cmsMain->publishall($request);
}
return Security::permissionFailure($this);
}
}

Related

What is the cleanest way to override a Wordpress plugins' hook instantiated with $this

I am trying to modify a Wordpress plugins' method without changing the plugin core code itself. I need to set some status flag in a different part of the website.
The method is defined in the plugin class like so:
add_action('plugins_action_name', [$this, 'local_method_name']);
The plugin method does not offer any actions or filters to hook into.
If this wasn't Wordpress but plain PHP I would write my own class, which extends the plugins' original class but has it's own method by the same name - but in Wordpress this gets me nowhere since I just move the problem from one plugin file to the next.
Is there a way to remove the plugins action altogether? And then redefine my own version of it?
Since the add_action has been called with $this - how would I remove it in some functions.php file?
Thanks
UPDATE
Indeed I want to learn how to handle different scenarios. Not expecting a one size fits all solution, but I do need to know how to avoid core changes, as each core-change complicates my deploy/update process exponentially.
Sure I will add some more details. My example plugin is WooCommerce Germanized Pro which comes with a WC_GZDP_Download_Handler::download() method which I needed to modify, in order to set some sort of "Warehouse downloaded the invoice" flag for each order. Which is later used to highlight new orders, filter orders that have been handed over to fulfilment etc.
There seems to be a Invoice Helper class which does in fact have a singleton pattern.
my modification in the download class is trivial:
public static function download( $pdf, $force = false ) {
... /* original code above, now my modifications */
if ($check_some_codition) {
$invoice = $pdf->get_invoice();
update_post_meta($invoice->get_id(), 'some-flag', 'some-value');
}
/* then resume original function (return the file to the browser) */
self::out( $filename, $file, $force );
}

Silverstripe Elemental Module redirects to frontend 404 page on element save

I use dnadesign/silverstripe-elemental 2.x-dev, and Silverstripe 4.0.1.
I created a module for page to hold all pages. This is how i added the extension to HomePage.
XYPage\Model\HomePage:
extensions:
- DNADesign\Elemental\Extensions\ElementalPageExtension
This is my HomePageController:
namespace XYpage\Controller;
use PageController;
class HomePageController extends PageController
{
}
This is my HomePageModel:
namespace XYpage\Model;
use Page;
use XYpage\Controller\HomePageController;
class HomePage extends Page
{
private static $table_name = 'HomePage';
/**
* As our controller resides in a different namespace we have to
overwrite this method
*
* #return string
*/
public function getControllerName()
{
return HomePageController::class;
}
}
I changed the template variable to $ElementalArea. I see the expected UI in the BackEnd.
Now if i save an single element in the backend i always get redirected into the front-end to the 404 Page.
When i switch back to the Backend the element is linked to the page.
If i fill in content to the WYSIWYG editor on the Content Element and save it the content gets displayed on the page in the frontend.
If i try to edit that element misses the WYSIWYG editor for the content.
I tried hard to fix this, red the docs but i donĀ“t see what i did wrong.
There is an issue with BetterButtons & DNADesign Elemental.
Just add this to disable BetterButtons for ElementContent in the meantime.
DNADesign\Elemental\Models\ElementContent:
better_buttons_enabled: false
You have two problems that I can see immediately:
1: Your YAML configuration is referencing XYPage\HomePage, where the class's namespace is actually XYPage\Model\HomePage. I suspect this is actually an error in your example rather than your actual project, since you say that the elemental editor is working in the CMS.
2: Your getControllerName() method is returning HomePageController::class which isn't imported in the class, so it will be resolving to the same namespace as the model (XYPage\Model\HomePageController). While this is the default/expected location for SiteTree controllers, overloading this code means it's all on you! Add use XYPage\Controller\HomePageController; to your class definition.
After a lot of debugging i found out what caused this some of that behavior. In my case one problem was that i used unclecheese/silverstripe-gridfield-betterbuttons with elemental.
The next Problem is a react error:

Integrating Mobile Money to Symfony

I have a Symfony application which I will like to integrate mobile money into. The problem is I cannot add PHP code to twig files and I am a complete newbie to this kind of challenge. The code reads:
<?php
require_once '/path/to/monetbil-php/monetbil.php';
// Setup Monetbil arguments
Monetbil::setAmount(500);
Monetbil::setCurrency('XAF');
Monetbil::setLocale('en'); // Display language fr or en
Monetbil::setPhone('');
Monetbil::setCountry('');
Monetbil::setItem_ref('2536');
Monetbil::setPayment_ref('d4be3535f9cb5a7aff1f84fa94e6f040');
Monetbil::setUser(12);
Monetbil::setFirst_name('KAMDEM');
Monetbil::setLast_name('Jean');
Monetbil::setEmail('jean.kamdem#email.com');
// Start a payment
// You will be redirected to the payment page
Monetbil::startPayment();
I am looking at adding this to App/Resources/Views/members/dashboard.html.twig
Twig is only ment to render your output. Put your (php) logic in your controller and/or create your own service. From your controller you will also render your Twig template with the variables you need but only to render the output that you want to show to the users.
If you're using a framework like Symfony, you shouldn't use require_once (except some exceptions). Read about autoloading and dependency injections (Symfony.com has excellent articles).
For some reason, the monetbil-php library doesn't use composer. I don't know why, but I can imagine three reasons: they don't know what it is (hello developers, it's 2017!), they hate other developers or the library hasn't been updated for years. Despite the recent commits, it looks like a very outdated library (why still supporting PHP 5.2? That's from the dark ages!). Sorry for this slightly offtopic rant, back to the question.
For now, copy the files to your project, give the file a namespace and use it in your project. I've opened an issue here because I think the developers should add a composer file if they want their users to use their library in a framework like Symfony.
The PHP code should be in your controller* since you can't use PHP in Twig and even if you could, you shouldn't. Monetbil defines business logic, so it shouldn't be in a template.
To use it in your controller:
/**
* Pay the bill
*
* #Route("/pay/{id}", name="payment")
* #Method("POST")
* #param Request $request
* #param Order $product
*
* #return JsonResponse
* #throws \Exception
*/
public function payAction(Request $request, Order $product)
{
Monetbil::setAmount(500);
//..
Monetbil::startPayment();
}
According to the comment, startPayment() will redirect, so there's nothing to return to the Twig template.
(*) Business logic in your template is considered as a bad practice, but you should prevent putting too many business logic in a controller too. If you have this example working, try to read about services so you can define the business logic in a framework-agnostic way. It makes maintaining your application (unit tests, Symfony updates, maybe switch to another framework?) easier.

How to copy Joomla administrator component as a site one?

I've installed Joomla v3.4.7 to test and prepare my project.I created a component 'HelloWorld' step by step according the official tutorial [https://docs.joomla.org/J3.x:Developing_an_MVC_Component/Using_the_database][1]
,and I succeeded to show the data list,and then the editing page to add or edit existing data, from the Administrator part, just like
localhost/joomla-test/administrator/index.php?option=com_helloworld
After finishing these, I simply copied the files in /Administrator/components/com_helloworld to /components/com_helloworld and overwrite previous files, and access the site component:
localhost/joomla-test/index.php?option=com_helloworld
It didn't work! I used firebug to debug and I got a
NetworkError: 500 Internal Server Error -
http://localhost/joomla-test/index.php?option=com_helloworld
error.... What's happened?
My code:
Site/helloworld.php:
<?php
// import joomla controller library
jimport('joomla.application.component.controller');
// Get an instance of the controller prefixed by HelloWorld
$controller = JControllerLegacy::getInstance('HelloWorld');
// Perform the Request task
$controller->execute(JFactory::getApplication()->input->getCmd('task'));
// Redirect if set by the controller
$controller->redirect();
site/controller.php
<?php
// No direct access to this file
defined('_JEXEC') or die;
// import Joomla controller library
jimport('joomla.application.component.controller');
/**
* General Controller of HelloWorld component
*/
class HelloWorldController extends JControllerLegacy
{
/**
* display task
*
* #return void
*/
protected $default_view = 'helloworlds';
public function display($cachable = false)
{
parent::display($cachable);
echo "controller";
return $this;
}
}
site/views/helloworlds/view.html.php:
<?php
// No direct access to this file
defined('_JEXEC') or die;
// import Joomla view library
jimport('joomla.application.component.view');
/**
* HelloWorlds View
*/
class HelloWorldViewHelloWorlds extends JViewLegacy
{
/**
* HelloWorlds view display method
* #return void
*/
function display($tpl = null)
{
// Get data from the model
$items = $this->get('Items');
$pagination = $this->get('Pagination');
// Check for errors.
if (count($errors = $this->get('Errors')))
{
JError::raiseError(500, implode('<br />', $errors));
return false;
}
// Assign data to the view
$this->items = $items;
$this->pagination = $pagination;
// Set the toolbar
$this->addToolBar();
// Display the template
parent::display($tpl);
}
/**
* Setting the toolbar
*/
protected function addToolBar()
{
JToolBarHelper::title(JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLDS'));
JToolBarHelper::deleteList('', 'helloworlds.delete');
JToolBarHelper::editList('helloworld.edit');
JToolBarHelper::addNew('helloworld.add');
}
}
Please help, thank you all.
It doesn't work this way (by just copying the folder over). You will have to install the component by packaging it and then installing it on the server. You will need to install the zipped component (that has the XML manifest file) on the server.
Try the following: download the basic HelloWorld component from Joomla, and then install it on your website, and then overwrite it with the files from your localhost.
Site and Administrator have slight differences; the most relevant are related to the template, since in Admin you can count on a standard layout; this is why in an administrator view.html you setup the toolbar and the sidemenu; on the frontend, you create menus pointing to views with configuration.
Your best bet is to create fresh files for controller and view, and then you can create your models inheriting from the administrator modules, that is the best for avoiding code duplication, and it will still leave you the maximum flexibility for customizing the views.
Toolbar cannot work in the front end. It's weird, yes, but if you look it is a separate thing in the administrator folder. It actually checks whether you are in admin as well. I once made a patch to removed the check but it turned out that it would have broken tons of components that had worked around this.
Second, there are many calls to things that rely on relative positioning or that might even explicitly require admin access.
Third, there are indeed some things that are slightly different because in the back end you basically never render the normal view, only the list view and the edit view.
If you want to do admin functions in the front end the best general approach is to look at how com_config, Com_templates and com_modules do it.

How to create a route with custom path in Symfony RoutingBundle (PHPCR)?

I'm currently researching Symfony CMF and PHPCR for a project I recently started. What I'm currently trying to figure out is how to create a Route and save it into the database. As far as I understand, I must use Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route and persist the element into the database. This works fine, but automatically generates a route path, which is not what I want. What I need to do is generate a custom route which links to a specific controller. Here is my code:
$em = $this->get('doctrine_phpcr.odm.document_manager');
$parent = $em->find(null, '/cms/routes');
$route = new \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route();
$route->setParentDocument($parent);
$route->setName('my_route_name');
$route->setDefault('_controller', 'AppBaseBundle:Frontend/Users:index');
$em->persist($route);
$em->flush();
If i execute this code, the generated route will be /cms/routes/my_route_name. From what I can see, you could use $route->setPath('/testing');, but that generates the following exception:
Can not determine the prefix. Either this is a new, unpersisted document or the listener that calls setPrefix is not set up correctly.
Does anybody have any ideas how to solve this?
In PHPCR, every document has a path where it is store. If you are familiar with doctrine ORM, the path has the role of the ID. The difference with ORM is that all documents (regardless of their type) live in the same tree. This is great, because your route can reference just anything, it is not limited to specific document types. But we need to create some structure with the paths. This is why we have the prefix concept. All routes are placed under a prefix (/cms/routes by default). That part of the document path is removed for the URL path. So repository path /cms/route/testing is the url domain.com/testing.
About your sample code: Usually, you want to configure the controller either by class of the content document or by route "type" attribute to avoid storing a controller name into your database to allow for future refactoring. A lot of this is explained in the [routing chapter of the CMF documentation][1] but the prefix is only used there, not explicitly explained. We need to improve the documentation there.
[1] http://symfony.com/doc/master/cmf/book/routing.html
I managed to find a way to overcome this issue. Because in my project I also have the RouteAutoBundle, I created a class which extends \Symfony\Cmf\Bundle\RoutingBundle\Doctrine\Phpcr\Route. Inside that class I added:
/**
* #PHPCR\Document(referenceable=true)
*/
class MenuRoute extends Route
{
protected $url;
/**
* Get $this->url
*
* #return mixed
*/
public function getUrl() {
return $this->url;
}
/**
* Set $this->url
*
* #param mixed $url
*/
public function setUrl($url) {
$this->url = $url;
}
}
After that I added this to cmf_routing_auto.yml:
App\MenuBundle\Document\MenuRoute:
uri_schema: /{getUrl}
token_providers:
getUrl: [content_method, { method: getUrl }]
So now one would just create an instance of MenuRoute (just like when using Route) and call the method setUrl($your_url) passing the desired url.
If anybody finds a better way, I'm opened to suggestions.

Categories