About incognito field #
Incognito field is a plugin that allows you to create fields that are hidden from users or blocked from editing. Such fields can be used to store data generated by custom modules, just as regular fields store user-generated content.
Thanks to the incognito field, your modules can be simpler - no need to create new database tables if you want to attach a bit of data to your elements - like entries, categories or users.
Example use case #
Recently, I was working on Craft website which had a frontend user system. Users could log-in to comment on articles and upvote them. Each article had category - and my client wanted to show users their "favourite category". Such category would be determined by counting visits of articles for each user - "favourite category" for the specific user would be one which articles had most visits from this user.
Plugin factory #
While such functionality required creating a custom module, there was no need for tinkering with database (like adding new tables) in order to store visits data - such data could be kept in the incognito field attached to user field layout. To store and retrieve data, I created a simple module using pluginfactory.io.
Plugin factory creates module (or plugin) scaffolding, saving you tons of work - you can learn more about modules in general in "Enhancing a Craft CMS 3 Website with a Custom Module" article on nystudio blog. Plugin factory allows you to include various functionalities in your modules - for "favourite category", I needed to include variables (functions accessible from Twig).
If it's your first module, you might also want to select "code comments" option - thanks to that, module source code will have comments describing how a variable can be used in the template. Instructions explaining how to install module will be available inside README.md
file that comes along with package generated by plugin factory.
Module code #
Module needs two variables:
saveArticleVisit
- this one saves article ID to incognito field attached to the user when he visits an article.getMostVisited
- this one returns "favourite category" for a specific user.
PHP code below defines these variables and should be placed within class declared inside plugin variable file - for module named "example module", file would be src/variables/ExampleModuleVariable
.
public $userFieldHandle = 'visitedarticles';
public $categoryFieldHandle = 'articleCategory';
public function saveArticleVisit($article){
$user = Craft::$app->getUser()->getIdentity();
if(!is_null($user)){
// get field value
$visitedJSON = $user->getFieldValue($this->userFieldHandle);
$visitedIds = (array) json_decode($visitedJSON);
//if there are not IDs saved yet
if(is_null($visitedIds)){
$visitedIds = array($article->id);
$user->setFieldValue($this->userFieldHandle, json_encode($visitedIds));
Craft::$app->elements->saveElement($user);
}
// if there were IDs saved
if(is_array($visitedIds) && !in_array($article->id, $visitedIds)){
$visitedIds[] = $article->id;
$user->setFieldValue($this->userFieldHandle, json_encode($visitedIds));
Craft::$app->elements->saveElement($user);
}
}
}
public function getMostVisited(){
$user = Craft::$app->getUser()->getIdentity();
if(!is_null($user)){
// get field value
$visitedJSON = $user->getFieldValue($this->userFieldHandle);
$visitedIds = (array) json_decode($visitedJSON);
if(is_array($visitedIds)){
// get entries from IDs savet to field
$entries = craft\elements\Entry::find()->id($visitedIds)->all();
//get categories of these entries
$categoryIds = [];
foreach ($entries as $key => $entry) {
if($entry->{$this->categoryFieldHandle}->exists()){
$category = $entry->{$this->categoryFieldHandle}->last();
$categoryIds[$key] = $category->id;
}
}
// find most often occuring category
if(!empty($categoryIds)){
$count = array_count_values ($categoryIds);
arsort($count);
$keys = array_keys($count);
// return category object
$popularCategory = craft\elements\Category::find()->id($keys[0])->one();
return $popularCategory;
}
}
}
}
How does it work? #
Let's take a look at saveArticleVisit
. First, we get current user, check if it does not equals null
(which means that nobody is logged in) and retrieve any already existing contents of the incognito field. Field handle is set in userFieldHandle
class property to visitedarticles
. Contents of the field will consist of a list of entry IDs serialized to JSON - so we need to parse field contents using json_decode
function.
If there were no IDs yet, json_decode
will return null. In that case, we create an array, put an ID of entry passed to saveArticleVisit
into it, encode it into JSON again, set field value and save user object. If some entry IDs were already saved in fields contents, we do pretty much the same - the only difference is that we append IDs to an existing array instead of creating a new one. It's also important to note that we do not save duplicate IDs - each entry ID is recorded only once.
Now for the getMostVisited
variable. It also retrieves user field value and decodes it from JSON. Then it uses retrieved array of IDs to queries DB for collection of entries matching these IDs. After that, using foreach
loop, IDs of categories of these entries are collected and placed into an array. Finally, using few PHP array-related functions, most often occurring category ID in the array is determined - this is ID of our "favourite category". Using this ID, the category element is queried from DB and returned into the template.
Using module #
Assuming that module is named "example module", this code should be placed in "article" entry template - it will record user visit:
{{ craft.exampleModule.saveArticleVisit(entry) }}
Now, to retrieve "favourite category" - this code can be used:
{{ craft.exampleModule.getMostVisited }}
While testing module, you can set the incognito field to behave like regular text field. Just remember to set it to hidden once you are done - to avoid any accidents when someone enters some random value into it using control panel profile page.