How to limit object access to $has_one Member in Silverstripe? - php

I'm working on a Silverstripe 4.3.1 project which has an object with an owner member attached via $has_one:
class Object extends DataObject
{
private static $has_one = [
'Member' => Member::class,
];
We want to limit the ability to view/ edit the object to Admins & the owner member.
Here is the code we've used:
public function canView($member = null)
{
return Permission::check('ADMIN') or
$this->Member()->ID === Security::getCurrentUser()->ID or
$this->Member()->ID === $member->ID;
}
public function canEdit($member = null)
{
return Permission::check('ADMIN') or
$this->Member()->ID === Security::getCurrentUser()->ID or
$this->Member()->ID === $member->ID;
}
From what I can tell this used to work, but recent framework upgrades or code changes have broken it.
We are currently getting the following PHP error:
Trying to get property of non-object on the lines containing $this->Member()->ID
Can anyone point me in the right direction for how to fix these errors?

It may be that some Object instances do no have a Member set. In those cases calling this->Member()->ID will error as Member() returns null.
First we should check if $this->Member() is for the Object. If it is not we can return false.
public function canView($member = null)
{
if (Permission::check('ADMIN')) {
return true;
}
if (!$this || !$this->exists()) {
return false;
}
if (!$this->Member() || !$this->Member()->exists()) {
return false;
}
if ($this->Member()->ID === $member->ID) {
return true;
}
if ($this->Member()->ID === Security::getCurrentUser()->ID) {
return true;
}
return false;
}
public function canEdit($member = null)
{
if (Permission::check('ADMIN')) {
return true;
}
if (!$this || !$this->exists()) {
return false;
}
if (!$this->Member() || !$this->Member()->exists()) {
return false;
}
if ($this->Member()->ID === $member->ID) {
return true;
}
if ($this->Member()->ID === Security::getCurrentUser()->ID) {
return true;
}
return false;
}

Related

Assigning the property $action of a class ($this) to an variable $conf, why it is an array?

There is the following function in the controller.php of framework thinkphp used in a project, where there is this line of codes: "$conf = $this->$action".
"if (property_exists($this, $action))" is checking if there exists property $action in the class $this. Form the next line of codes "if (key_exists('login', $conf))", it looks to me that $conf is an array.
My question is: what exactly is "$conf = $this->$action" doing, assigning an array (the property $action? why $action is an array?) to the variable $conf?
public function checkAccessControl()
{
if (Config::get('frame.controller.checkAccessControl.exec')) {
$action = $this->request ->action(true);
if (property_exists($this, $action)) {
$conf = $this->$action;
if (key_exists('login', $conf)) {
if ($conf['login'] === true && $this->if_login === false) {
$this->access_control_check = false;
$this->response(["error"=>"未登录用户无权访问"],[],401,'json');//要求用户的身份认证
} else {
$this->access_control_check = true;
}
} else {
$this->access_control_check = false;
$this->response(["error"=>"代码错误:未标注访问类型"],[],500);//服务器内部错误,无法完成请求(未在控制器中标注login的访问控制)
}
} else {
$this->access_control_check = false;
$this->response(["error"=>"代码错误:未标注访问控制"],[],500,'json',['action'=>$this->request->action(true)]);//服务器内部错误,无法完成请求(未在控制器中标注方法的访问控制)
}
} else {
$this->access_control_check = Config::get('frame.controller.checkAccessControl.default.access_control_check');
}
}

Conditionally load public and admin code with AJAX working on both sides on WordPress

I'm trying to conditionally load my front-end and admin area code in WordPress plugin, so the file and class that creates admin area will on load on admin side and file and class that is needed to be run on front-end will run only on front-end and won't touch anything on admin area.
I tried to use is_admin() conditional:
if (!is_admin()) {
require_once(plugin_dir_path(dirname(__FILE__)) . 'public/class-public.php');
$this->Public = new Public();
} else {
require_once(plugin_dir_path(dirname(__FILE__)) . 'admin/class-admin.php');
$this->Admin = new Admin();
}
code loading was fine, but AJAX was not working on public side, as AJAX requests bound to either wp_ajax_ or wp_ajax_nopriv_ actions are executed in the WP Admin context. So I decided to create my own isAdmin() function:
public static function isAdmin() {
$currentUrl = set_url_scheme(
sprintf(
'http://%s%s',
$_SERVER['HTTP_HOST'],
$_SERVER['REQUEST_URI']
)
);
$adminUrl = strtolower(admin_url());
$referrer = strtolower(wp_get_referer());
if (strpos($currentUrl, $adminUrl) === 0) {
if (strpos($referrer, $adminUrl) === 0) {
return true;
} else {
if (function_exists('wp_doing_ajax')) {
return !wp_doing_ajax();
} else {
return !(defined('DOING_AJAX') && DOING_AJAX);
}
}
} else {
if (!defined('REST_REQUEST') || !REST_REQUEST) {
return false;
}
return (isset($_REQUEST['context']) && $_REQUEST['context'] === 'edit');
}
}
code loading was still fine, but now AJAX was working on the public side and not working on the admin side.
So, how can I prevent loading public code on admin area code and vice versa with AJAX working on both sides?
I managed to resolve this issue by also checking for public interface. I created new function isPublic() to check if it is public. So here is my final code:
if ($this->isPublic()) {
require_once(plugin_dir_path(dirname(__FILE__)) . 'public/class-public.php');
$this->Public = new Public();
} elseif ($this->isAdmin()) {
require_once(plugin_dir_path(dirname(__FILE__)) . 'admin/class-admin.php');
$this->Admin = new Admin();
}
and here are helper isPublic() and isAdmin() functions:
public static function isAdmin() {
if (function_exists('is_admin') && is_admin()) {
return true;
} else {
if (strpos($_SERVER['REQUEST_URI'], 'wp-admin') !== false) {
return true;
} else {
return false;
}
}
}
public static function isPublic() {
if (function_exists('is_admin') && is_admin()) {
if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
return true;
} else {
return false;
}
} else {
if (strpos($_SERVER['REQUEST_URI'], 'wp-admin') !== false) {
if (strpos($_SERVER['REQUEST_URI'], 'admin-ajax.php') !== false) {
return true;
} else {
return false;
}
} else {
return true;
}
}
}

Manipulating passed arguments in php

Im not so experienced in php , Im using codeigniter to write my application , I have my own library and within my library there are three functions/methods that passes there arguments in one function which is in one of my models , my question is how will i manipulate/trick the method in my model to know exactly which function among the three in my library has passed the value and return the correct value ..
1st function
public function id_exist ($id) {
if(empty($id)) {
return FALSE;
}
return $this->library_model->this_exist($id);
}
2nd function
public function group_exist($group) {
if(empty($group)){
return FALSE;
}
return $this->library_model->this_exist($group);
}
with the 3rd same as the above 2
in my model
public function this_exist ($item) {
if(empty($item) || !isset($item)) {
return FALSE;
}
// here is where i need to know which function has passed the argument so that i can work with it and return the correct value from the database
}
Might be dirty, might be not sophisticated, but why not passing another argument which tells exactly the origin?
public function id_exist ($id) {
if(empty($id)) {
return FALSE;
}
return $this->library_model->this_exist('id', $id);
}
public function group_exist($group) {
if(empty($group)){
return FALSE;
}
return $this->library_model->this_exist('group', $group);
}
Model:
public function this_exist ($origin, $item) {
if(empty($item) || !isset($item)) {
return FALSE;
}
if($origin == 'id'){
// do something
}
elseif($origin == 'group') {
// do something else
}
}

How to return false when chaining methods

I have a validation class which uses method chaining. I would like to be able to do single checks with TRUE/FALSE like this:
if ($obj->checkSomething()) {}
But also chain methods like this:
if ($obj->checkSomething()->checkSomethingElse()) {}
The problem however is that if one method returns FALSE, it will not send back an object and thus breaks the method chaining which ends with this error:
Fatal error: Call to a member function checkSomething() on a non-object in ...
Do I have to pick either single method return calls or method chaining or is there a workaround?
One idea would be to set an internal flag to indicate success or failure, and access it via another method, while checking that flag in each method and not doing anything if it's set. E.g.:
class A {
private $valid = true;
public function check1() {
if (!$this->valid) {
return $this;
}
if (!/* do actual checking here */) {
$this->valid = false;
}
return $this;
}
public function check2() {
if (!$this->valid) {
return $this;
}
if (!/* do actual checking here */) {
$this->valid = false;
}
return $this;
}
public function isValid() {
return $this->valid;
}
}
// usage:
$a = new A();
if (!$a->check1()->check2()->isValid()) {
echo "error";
}
To minimize the boilerplate checking in each function, you could also use the magic method __call(). E.g.:
class A {
private $valid;
public function __call($name, $args) {
if ($this->valid) {
$this->valid = call_user_func_array("do" . $name, $args);
}
return $this;
}
private function docheck1() {
return /* do actual checking here, return true or false */;
}
private function docheck2() {
return /* do actual checking here, return true or false */;
}
public isValid() {
return $this->valid;
}
}
The usage would be same as above:
$a = new A();
if (!$a->check1()->check2()->isValid()) {
echo "error";
}
I believe you're looking to have an instance evaluate as true/false based on the outcome of validation.
While some languages allow you to override the boolean value of an instance, php does not (except for casting to string, that is. See PHP's Magic Methods).
Also, the booleans page in the PHP Manual has a list of things that evaluate to false, but it doesn't give any method of overriding the behavior either.
That being said, I'd suggest going with JRL's idea, and construct a chain of validation rules, then 'execute' it with a function that returns the boolean needed in your if statement.
You could wrap them up in a subclass, perhaps.
e.g. if you have
class Validate {
public function checkSomething($data) {
if ($data === $valid) {
return true;
}
return false;
}
public function checkSomethingElse($data) {
if ($data === $valid) {
return true;
}
return false;
}
}
You could do this:
class ValidateChain extends Validate {
protected $valid = true;
public function checkSomething($data) {
if (false === parent::checkSomething($data)) {
$this->valid = false;
}
return $this;
}
public function checkSomethingElse($data) {
if (false === parent::checkSomethingElse($data)) {
$this->valid = false;
}
return $this;
}
public function getIsValid() {
return $this->valid;
}
}
$v = new ValidationChain();
$valid = $v->checkSomething()->checkSomethingElse()->getIsValid();
Quick and dirty, E&OE. And you'd probably need to add a way to find out which bits weren't valid, etc.

Is this possible in PHP?

Consider the following PHP snippet:
<?php
class Is
{
function __get($key)
{
$class = __CLASS__ . '_' . $key;
if (class_exists($class) === true)
{
return $this->$key = new $class();
}
return false;
}
function Domain($string)
{
if (preg_match('~^[0-9a-z\-]{1,63}\.[a-z]{2,6}$~i', $string) > 0)
{
return true;
}
return false;
}
}
class Is_Domain
{
function Available($domain)
{
if (gethostbynamel($domain) !== false)
{
return true;
}
return false;
}
}
$Is = new Is();
var_dump($Is->Domain('google.com')); // true
var_dump($Is->Domain->Available('google.com')); // false
?>
Is it possible to call the Available() method like this (and still return solely true or false if the Available method is not called)?
var_dump($Is->Domain('google.com')->Available()); // false
If yes, how?
EDIT: Would this do the trick?
class Is
{
function __get($key)
{
// same as before
}
function Domain($string)
{
if (preg_match('~^[0-9a-z\-]{1,63}\.[a-z]{2,6}$~i', $string) > 0)
{
return (bool) $this->Domain;
}
return false;
}
}
class Is_Domain
{
function __toString()
{
return true;
}
function Available($domain)
{
if (gethostbynamel($domain) !== false)
{
return true;
}
return false;
}
}
Thanks in Advance!
PS: This snippet is truncated, so don't expect it to make it a lot of sense just by its own.
Essentially you want a method to return either a bool or an object based on whether a subsequent method call to the result is going to occur. I don't think this will be possible without some massive hack (e.g. reading the PHP file in yourself and looking ahead), and it shouldn't be because your objects shouldn't be worrying about the context in which they are used.
Instead you could get the first call to return an object which is relevant in both cases, e.g. DomainLookupResult, which has two methods e.g. Exists() and IsAvailable(). You could then do:
$result = $Is->Domain('google.com');
$isValid = $result->Exists();
$isAvaliable = $result->IsAvailable();
//or chaining:
$isValid = $Is->Domain('google.com')->Exists();
$isAvailable = $Is->Domain('google.com')->IsAvailable();
You can only chain method calls if they return an object!
This is because you can only call methods on objects.
The problem with your code is that the methods return a non object value, either true or false. And the problem is not in any way solved better by chaining methods. You should use that where its applicable. Like chaining many setters, NOT getters which the methods you want to use essentially is.
var_dump($Is->Domain->Available('google.com')); // false
//is the same as
$res = $Is->Domain;
$res = $res->Available('google.com'));
var_dump($res);
So you see the first res is a boolean true or false, and you can not call a method on that.
edit
This might be a "solution". Not a good solution though since this is better without chaining.
class Domain
{
public $domain;
function setDomain($domain) {
$this->domain = $domain;
return $this;
}
function isDomain($domain = null) {
if (is_string($domain)) {
$this->setDomain($domain);
}
$result = gethostbynamel($this->domain) !== false;
return new Result($this, $result);
}
function isValid() {
$result = (bool) preg_match('', $this->domain);
return new Result($this, $result)
}
}
class Result
{
public $result;
public $object;
public function __construct($object, $result)
{
$this->result = $result;
$this->object = $object;
}
public function __call($method, $arguments)
{
if (is_object($this->result)) {
return call_user_func_array(array($this->result, $method), $arguments);
}
if (!$this->result) {
return $this;
}
return call_user_func_array(array($this->object, $method), $arguments);
}
}
$domain = new Domain();
var_dump($domain->isValid('google.com')->isAvailable()->result);
/edit
This will solve your problem above.
var_dump($Is->Domain('validandfreedomain.com') && $Is_Domain->Available('validandfreedomain.com')); // true
If you desperately want to chain a method for this problem you could make it more like this.
class Domain
{
public $domain;
function setDomain($domain) {
$this->domain = $domain;
return $this;
}
function isAvailable() {
return gethostbynamel($this->domain) !== false;
}
function isValid() {
return (bool) preg_match('', $this->domain);
}
}
$domain = new Domain();
$result = $domain->setDomain('validandfreedomain.com')->isValid() && $domain->isAvailable();
It is possible, if your function returns an object, you can call its method, and so on (see method chaining). The only limitation is - as far as a I know - is that you cannot chain calls from an object created by new ( new Object()->method1()->method2() ).
As for your example, I see no point in using either the dynamic class, or method chaining stuff.

Categories