-2

I'm wondering what the best approach is to control which model attributes a given user is allowed to view.

To control which attributes they are allowed to modify I'm of course using scenarios, but sometimes they should be allowed to view attributes which they are not allowed to modify, so I can't just use the same list of attributes.

I want to control it at a central point, so preferably within the model I would guess.

What is the best way, or Yii intended method, to approach this?

TheStoryCoder
  • 3,403
  • 6
  • 34
  • 64
  • have a look at [fields](http://www.yiiframework.com/doc-2.0/yii-base-model.html#fields()-detail) method. it nicely filters out any fields you want to hide in a [rest-api context](http://www.yiiframework.com/doc-2.0/guide-rest-resources.html#overriding-fields) however in other use cases (grids forms) you'll have to pretty much manage it manually – csminb Jul 25 '17 at 09:23
  • @csminb Not sure how I would use that since that only seems to be only for `toArray`. Feel free to post an answer with example. – TheStoryCoder Jul 26 '17 at 11:06

1 Answers1

0

I was thinking that I needed something similar to scenarios so, building on that idea I have now tried to make a solution where I create a method called viewable on my model, which returns a list of attributes that should be visible for the current scenario of the model. For example:

public function viewable() {
    $scenario = $this->getScenario();

    if ($scenario == self::SCENARIO_DEFAULT) {
        return [];

    } elseif ($scenario == self::SCENARIO_EV_ADMIN) {
        return $this->attributes();  //admin is allowed to see all attributes on the model

    } elseif ($scenario == self::SCENARIO_EV_ORGANIZER_INSERT || $scenario == self::SCENARIO_EV_ORGANIZER_UPDATE) {
        $attributes = $this->activeAttributes();  //use list of attributes they can edit as the basis for attributes they can view
        array_push($attributes, 'ev_approved', 'ev_status');  //add a few more they are allowed to view
        return $attributes;

    } else {
        return [];

    }
}

Then eg. in GridView or DetailView I pass the list of columns/attributes through a helper that will filter out any attributes that were not returned by viewable. Eg.:

    'attributes' => MyHelper::filterAttributes([
        'eventID',
        [
            'attribute' => 'organizerID',
            'value' => \app\models\Organizer::findOne($model->organizerID)['org_name'],
        ],
        'ev_name',
        ....
    ], $model->viewable()),

My helper method being like this:

public static function filterAttributes($all_attributes, $attributes_to_keep) {
    $output = [];
    foreach ($all_attributes as $value) {
        if (is_string($value)) {
            $colon = strpos($value, ':');
            if ($colon === false) {
                $name = $value;
            } else {
                $name = substr($value, 0, $colon);
            }
        } elseif (is_array($value)) {
            if ($value['attribute']) {
                $name = $value['attribute'];
            } elseif ($value['class']) {
                // always leave special entries intact (eg. class=yii\grid\ActionColumn)
                $output[] = $value;
                continue;
            } else {
                new UserException('Attributes name not found when filtering attributes.');
            }
        } else {
            new UserException('Invalid value for filtering attributes.');
        }

        if (in_array($name, $attributes_to_keep)) {
            $output[] = $value;
        }
    }
    return $output;
}

And in create.php/update.php (or _form.php actually) I do this:

$editAttribs = $model->activeAttributes();
$viewAttribs = $model->viewable();

....

if (in_array('organizerID', $viewAttribs)) {
    echo $form->field($model, 'organizerID')->textInput(['disabled' => !in_array('organizerID', $editAttribs) ]);
}

....

Feedback is welcome!

TheStoryCoder
  • 3,403
  • 6
  • 34
  • 64