I have a DataObject class called AdminUpload that stores two variables: UploadDate (which is always going to bet set to the current date) and Total, which is an int.
The function updateUploads() is called and stores the current date and increments the Total by 1 each time its called.
<?php
class AdminUpload extends DataObject {
private static $db = array(
'UploadDate' => 'Date',
'Total' => 'int'
);
// Tell the datagrid what fields to show in the table
private static $summary_fields = array(
'ID' => 'ID',
'UploadDate' => 'Current Date',
'Total' => 'Version Number'
);
public function updateUploads(){
$uploadDate = SS_Datetime::now();
$this->UploadDate = $uploadDate;
$this->Total++;//increment the value currently stored in the database each time
$this->write();
}
}
What I want to to is, when someone uploads a new image in the admin view, then the updateCache() function is called during the onAfterWrite() process. I only want to maintain one entry in the database, though, so every time I upload an image, I want to have just one entry in the AdminUpload database table.
public function onAfterWrite(){
$updateGallery = parent::onAfterWrite();
$adminUploading = AdminUpload::get();
$adminUploading -> updateUploads();
return $updateGallery;
}
I've never tried to do a function call in SilverStripe like this--it seems simple enough but since I am not going to add a new entry to the database with each call to the updateUploads() function, that's where I'm stuck. Any tips would be helpful...
It is incorrect approach to create a whole table for just one record. If you were going to use theses two fields on a page, then adding them to that page (create a new page type) would be a better idea.
If you are talking about file uploads, then you can always query this information directly from database.
$uploadedFilesCount = File::get()->count();
$lastUploadedFileDate = File::get()->sort('CreatedDate', 'DESC')->first()->CreatedDate;
onAfterWrite is a hook and used from DataExtension. There are cases when hooks are called directly on DO and then on extensions.
Your extension code might look like this to handle 'onCreated' state:
class UploadsCounter extends DataExtension {
protected $isCreating = false;
public function onBeforeWrite() {
if (!$this->owner->isInDB()) {
$this->isCreating = true;
}
}
// called on validation or database error
public function onAfterSkippedWrite() {
$this->isCreating = false;
}
public function onAfterWrite() {
if (!$this->isCreating) return;
$this->isCreating = false;
$adminUploading = AdminUpload::get()->first();
if (!$adminUploading ) {
$adminUploading = new AdminUpload();
$adminUploading->write();
}
$adminUploading->updateUploads();
}
}
You should define UploadsCounter extension on the dataobject that you are going to count, for example:
mysite/_config/config.yml
File:
extensions:
- UploadsCounter
Related
Hi I have problem when i tried to save attribute of model to database. I write in OctoberCMS and i have this function:
public function findActualNewsletter()
{
$actualNewsletter = Newsletter::where('status_id', '=', NewsletterStatus::getSentNowStatus())->first();
if (!$actualNewsletter) {
$actualNewsletter = Newsletter::where('send_at', '<=', date('Y-m-d'))->where('status_id', NewsletterStatus::getUnsentStatus())->first();
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
dd($actualNewsletter);
}
return $actualNewsletter;
}
getSentNowStatus()=2;
getUnsentStatus()=1;
dd($actualNewsletter) in my if statement show that status_id = 2 But in database i still have 1. I used this function in afterSave() so i dont need:
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
$actualNewsletter->save();
becosue i have error then i use save in save.
Of course i filled table $fillable =['status_id']. And now i dont know why its not save in database when it go to my if. Maybe someone see my mistake?
If you are trying to modify the model based on some custom logic and then save it, the best place to put it is in the beforeSave() method of the model. To access the current model being saved, just use $this. Below is an example of the beforeSave() method being used to modify the attributes of a model before it gets saved to the database:
public function beforeSave() {
$user = BackendAuth::getUser();
$this->backend_user_id = $user->id;
// Handle archiving
if ($this->is_archived && !$this->archived_at) {
$this->archived_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle publishing
if ($this->is_published && !$this->published_at) {
$this->published_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle unarchiving
if ($this->archived_at && !$this->is_archived) {
$this->archived_at = null;
}
// Handle unpublishing, only allowed when no responses have been recorded against the form
if ($this->published_at && !$this->is_published) {
if (is_null($this->responses) || $this->responses->isEmpty()) {
$this->published_at = null;
}
}
}
You don't have to run $this->save() or anything like that. Simply modifying the model's attributes in the beforeSave() method will accomplish what you desire.
Note: version 3.1
Trying to validate the input of this fields from CMS:
expiry date must be after publish date.
I tried even a simple validation (required) but doesn't work.
public function updateCMSFields(FieldList $fields) {
$publishDatetimeField = new DatetimeField( 'PublishDate', 'Publish Date' );
$expiryDatetimeField = new DatetimeField( 'ExpiryDate', 'Expiry Date' );
$fields->addFieldToTab('Root.Options', $publishDatetimeField);
$fields->addFieldToTab('Root.Options', $expiryDatetimeField);
}
public function getCMSValidator(){
return new RequiredFields('publishDatetimeField');
}
I can manipulate the values and compare them, but i can't access them.
Any ideas, are welcome.
You should be able to use the basic validator in terms of checking a field isn't blank... but you should be using the name of the field "PublishDate", not "publishDatetimeField".
In general this is how validations are fully set in silverstripe...
class MyDataObject extends DataObject {
static $db = array(
'MyDateField' => 'SS_DateTime',
);
function getCMSValidator() {
return new MyDataObject_Validator();
}
}
class MyDataObject_Validator extends RequiredFields {
function php($data) {
$bRet = parent::php($data);
//do checking here
if (empty($data['MyDateField']))
$this->validationError('MyDateField','MyDateField cannot be empty','required');
return count($this->getErrors());
}
}
you can check for what the data...
die(var_dump($data));
and this should be a string in MYSQL format... like "2016-03-24 11:41:00"
I want to write a function which will take a series of fields and input different values into a different databases. Right now it is only two separate database entries but I hope to implement more later. I want to input a new Saint into one table and then, if the user fills in the 'ofRegion' field, I want that to be stored in a different table. My problem comes about when the model tries to input the information for 'ofRegion.' I get a MySQL error ( 1054 ) stating there is an unknown column. I can see by the MySQL error that it is trying to input all the information from the previous entry as well as the new information. How do I clear the old information? Can I even do this or will I need multiple models for each table I want to enter information into?
Model Functions
public function input_saint($newSaintID)
{
//grab values from post stream
$this->saintID = $newSaintID;
$this->active = 1;
$this->nameFirst = $this->input->post('nameFirst');
$this->nameLast = $this->input->post('nameLast');
$this->gender = $this->input->post('gender');
$this->martyr = $this->input->post('martyr');
$this->nationality = $this->input->post('nationality');
$this->feastMonth = $this->input->post('feastMonth');
$this->feastDay = $this->input->post('feastDay');
$this->about = $this->input->post('about');
//insert information into the saint table
$this->db->insert('saint_table', $this);
}
public function set_of_region($newSaintID)
{
$this->saintID = $newSaintID;
$this->ofRegion = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $this);
}
Controller Function
public function saint_input()
{
//Check if user is logged in, if they are not, send them to the login screen
if($this->session->userdata('logged_in') == FALSE)
{
redirect('base/');
}
$this->load->library('form_validation');
//load Saint model and get the nation list
$this->load->model('saint_model');
//Load the nation list
$data['nationList'] = $this->saint_model->get_nations();
if($this->form_validation->run('saint_input')==FALSE)
{
$this->load->view('std/top');
$this->load->view('forms/saint_input', $data);
$this->load->view('std/bottom');
}
else
{
//generate saintID
$newSaintID = $this->saint_model->get_largest_saintID();
$newSaintID++;
$this->saint_model->input_saint($newSaintID);
//if specified, record the ofRegion
if($this->input->post('ofRegion') != NULL)
{
$this->saint_model->set_of_region($newSaintID);
}
//Send the user to this saint's single view page for review
redirect('base/display_one_saint/'.$newSaintID);
}
}
Thank you very much for your time and work!
That's because you're using $this as an array to store data before inserting it. $this is a reference to the object as a whole and any properties that you set on it will persist until they are unset. One solution is to change to an array for the insert() function as below:
public function set_of_region($newSaintID)
{
$ins_arr['saintID'] = $newSaintID;
$ins_arr['ofRegion'] = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $ins_arr);
}
I have users' table users, where I store information like post_count and so on. I want to have ~50 badges and it is going to be even more than that in future.
So, I want to have a page where member of website could go and take the badge, not automatically give him it like in SO. And after he clicks a button called smth like "Take 'Made 10 posts' badge" the system checks if he has posted 10 posts and doesn't have this badge already, and if it's ok, give him the badge and insert into the new table the badge's id and user_id that member couldn't take it twice.
But I have so many badges, so do I really need to put so many if's to check for all badges? What would be your suggestion on this? How can I make it more optimal if it's even possible?
Thank you.
optimal would be IMHO the the following:
have an object for the user with functions that return user specific attributes/metrics that you initialise with the proper user id (you probably wanna make this a singleton/static for some elements...):
<?
class User {
public function initUser($id) {
/* initialise the user. maby load all metrics now, or if they
are intensive on demand when the functions are called.
you can cache them in a class variable*/
}
public function getPostCount() {
// return number of posts
}
public function getRegisterDate() {
// return register date
}
public function getNumberOfLogins() {
// return the number of logins the user has made over time
}
}
?>
have a badge object that is initialised with an id/key and loads dependencies from your database:
<?
class Badge {
protected $dependencies = array();
public function initBadge($id) {
$this->loadDependencies($id);
}
protected function loadDependencies() {
// load data from mysql and store it into dependencies like so:
$dependencies = array(array(
'value' => 300,
'type' => 'PostCount',
'compare => 'greater',
),...);
$this->dependencies = $dependencies;
}
public function getDependencies() {
return $this->dependencies;
}
}
?>
then you could have a class that controls the awarding of batches (you can also do it inside user...)
and checks dependencies and prints failed dependencies etc...
<?
class BadgeAwarder {
protected $badge = null;
protected $user = null;
public function awardBadge($userid,$badge) {
if(is_null($this->badge)) {
$this->badge = new Badge; // or something else for strange freaky badges, passed by $badge
}
$this->badge->initBadge($badge);
if(is_null($this->user)) {
$this->user = new User;
$this->user->initUser($userid);
}
$allowed = $this->checkDependencies();
if($allowed === true) {
// grant badge, print congratulations
} else if(is_array($failed)) {
// sorry, you failed tu full fill thef ollowing dependencies: print_r($failed);
} else {
echo "error?";
}
}
protected function checkDependencies() {
$failed = array();
foreach($this->badge->getDependencies() as $depdency) {
$value = call_user_func(array($this->badge, 'get'.$depdency['type']));
if(!$this->compare($value,$depdency['value'],$dependency['compare'])) {
$failed[] = $dependency;
}
}
if(count($failed) > 0) {
return $failed;
} else {
return true;
}
}
protected function compare($val1,$val2,$operator) {
if($operator == 'greater') {
return ($val1 > $val2);
}
}
}
?>
you can extend to this class if you have very custom batches that require weird calculations.
hope i brought you on the right track.
untested andp robably full of syntax errors.
welcome to the world of object oriented programming. still wanna do this?
Maybe throw the information into a table and check against that? If it's based on the number of posts, have fields for badge_name and post_count and check that way?
Once you're OK with basic record form built after example from Tutorial, you realize you want more professionally designed Record Form. E.g. I don't want to duplicate record form for the same table in User and Admin areas.
1) Does anyone use some mechanism, possibly inheritance, to reduce duplication of almost similar admin and user forms? Is that burdensome or sometimes you better just do with copy-pasting?
2) Has anyone considered it to be a good idea to build some basic Record class
that can determine that among several record forms on this page, the current post is addressed specifically to this record form
that can distinguish between Edit or Delete buttons clicks in some organized fashion.
3) My current practice includes putting all form config code (decorators, validations, initial values) into constructor and form submit handling is put into a separate ProcessSubmit() method to free controller of needless code.
All the above addresses to some expected Record Form functionality and I wonder if there is any guideline, good sample app for such slightly more advanced record handling or people are still reinveting the wheel. Wondering how far you should go and where you should stop with such impovements...
Couple of suggestions:
First of all - Use the init() function instead of constructors to add your elements when you are subclassing the form. The init() function happens after the parameters you pass to the class are set.
Second - Instead of subclassing your form - you can just set an "option" to enable the admin stuff:
class My_Record_Form extends Zend_Form {
protected $_record = null;
public function setRecord($record) {
$this->_record = $record;
}
public function getRecord() {
if ($this->_record === null || (!$this->_record instanceOf My_Record)) {
throw new Exception("Record not set - or not the right type");
}
return $this->_record;
}
protected $_admin = false;
public function setAdmin($admin) {
$this->_admin = $admin;
}
public function getAdmin() { return $this->_admin; }
public function init() {
$record = $this->getRecord();
$this->addElement(......);
$this->addElement(......);
$this->addElement(......);
if ($this->getAdmin()) {
$this->addElement(.....);
}
$this->setDefaults($record->toArray());
}
public function process(array $data) {
if ($this->isValid($data)) {
$record = $this->getRecord();
if (isset($this->delete) && $this->delete->getValue()) {
// delete button was clicked
$record->delete();
return true;
}
$record->setFromArray($this->getValues());
$record->save();
return true;
}
}
}
Then in your controller you can do something like:
$form = new My_Record_Form(array(
'record'=>$record,
'admin'=>My_Auth::getInstance()->hasPermission($record, 'admin')
));
There is nothing "wrong" with making a My_Record_Admin_Form that handles the admin stuff as well - but I found this method keeps all the "record form" code in one single place, and a bit easier to maintain.
To answer section 2: The edit forms in my code are returned from a function of the model: $record->getEditForm() The controller code ends up looking a little like this:
protected $_domain = null;
protected function _getDomain($allowNew = false)
{
if ($this->_domain)
{
return $this->view->domain = $this->_domain;
} else {
$id = $this->_request->getParam('id');
if (($id == 'new' || $id=='') && $allowNew)
{
MW_Auth::getInstance()->requirePrivilege($this->_table, 'create');
$domain = $this->_table->createRow();
} else {
$domain = $this->_table->find($id)->current();
if (!$domain) throw new MW_Controller_404Exception('Domain not found');
}
return $this->view->domain = $this->_domain = $domain;
}
}
public function editAction()
{
$domain = $this->_getDomain(true);
MW_Auth::getInstance()->requirePrivilege($domain,'edit');
$form = $domain->getEditForm();
if ($this->_request->isPost() && $form->process($this->_request->getPost()))
{
if ($form->delete && $form->delete->getValue())
{
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'index',
), null, true));
} else {
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'view',
'id'=>$form->getDomain()->id,
), null, true));
}
}
$this->view->form = $form;
}
So - the actual id of the record is passed in the URI /domain/edit/id/10 for instance. If you were to put multiple of these forms on a page - you should make sure to set the "action" attribute of the form to point to an action specific to that form.
I created a SimpleTable extends Zend_Db_Table and SimpleForm extends Zend_Db_Form classes. Both of these assume that your table has an auto-incrementing ID column.
SimpleTable has a saveForm(SimpleForm $form) function which uses the dynamic binding to match form element names to the columns of the record. I also included an overridable saveFormCustom($form) for any special handling.
The SimpleForm has an abstract setup() which must be overridden to setup the form. I use the init() to do the initial setup (such as adding the hidden ID field).
However, to be honest, I really don't like using the Zend_Form object, I feel like that should be handled in the View, not the Model or Controller.