Fatal error: Declaration of CRMCoreContactController::save($contact) must be compatible with EntityAPIController::save - drupal-7

I recently installed CRM Core and all of its missing modules needed to run it. Sadly I need this module for the project that I am working on but the second I installed them I got this error.
Fatal error: Declaration of CRMCoreContactController::save($contact) must be compatible with EntityAPIController::save($entity, ?DatabaseTransaction $transaction = NULL) in /opt/lampp/htdocs/drupal/modules/crm_core/modules/crm_core_contact/includes/crm_core_contact.controller.inc on line 111
I went back in the code and I couldn't see what to change. Line 111 is the ver last line of the code. Ill paste the code as well maybe someone out there knows how to solve this, please.
<?php
/**
* CRM Contact Entity Class.
*/
class CRMCoreContactEntity extends Entity {
protected function defaultLabel() {
return crm_core_contact_label($this);
}
protected function defaultUri() {
return array(
'path' => 'crm-core/contact/' . $this->identifier(),
'options' => array(
'absolute' => TRUE,
),
);
}
/**
* Method for de-duplicating contacts.
*
* Allows various modules to identify duplicate contact records through
* hook_crm_core_contact_match. This function should implement it's
* own contact matching scheme.
*
* #return array
* Array of matched contact IDs.
*/
public function match() {
$checks = & drupal_static(__FUNCTION__);
$matches = array();
if (!isset($checks->processed)) {
$checks = new stdClass();
$checks->engines = module_implements('crm_core_contact_match');
$checks->processed = 1;
}
// Pass in the contact and the matches array as references.
// This will allow various matching tools to modify the contact
// and the list of matches.
$values = array(
'contact' => &$this,
'matches' => &$matches,
);
foreach ($checks->engines as $module) {
module_invoke($module, 'crm_core_contact_match', $values);
}
// It's up to implementing modules to handle the matching logic.
// Most often, the match to be used should be the one
// at the top of the stack.
return $matches;
}
}
/**
* #file
* Controller class for contacts.
*
* This extends the DrupalDefaultEntityController class, adding required
* special handling for contact objects.
*/
class CRMCoreContactController extends EntityAPIController {
public $revisionKey = 'vid';
public $revisionTable = 'crm_core_contact_revision';
/**
* Create a basic contact object.
*/
public function create(array $values = array()) {
global $user;
$values += array(
'contact_id' => '',
'vid' => '',
'uid' => $user->uid,
'created' => REQUEST_TIME,
'changed' => REQUEST_TIME,
);
return parent::create($values);
}
/**
* Update contact object before saving revision.
*/
protected function saveRevision($entity) {
if (!isset($entity->log)) {
$entity->log = '';
}
$entity->is_new_revision = TRUE;
$entity->uid = $GLOBALS['user']->uid;
return parent::saveRevision($entity);
}
/**
* Updates 'changed' property on save.
*/
public function save($contact) {
$contact->changed = REQUEST_TIME;
// Storing formatted contact label for autocomplete lookups.
$contact->name = crm_core_contact_label($contact);
return parent::save($contact);
}
}

Changing
public function save($contact)
to
public function save($contact, DatabaseTransaction $transaction = NULL)
should work.

You need to switch from PHP 7.x+ to PHP 5.6. This will resolve this error.
Can't give you more advice on how to downgrade without more details on what system you're running but there are many guides out there on this topic.

Related

CakePHP 4.2 Annotate: incorrect app namespace

I'm using CakePHP version 4.2 and am noticing some odd behavior from the annotate script that comes bundled with the API. For one component, the annotate script wants to default to the App\ domain that is CakePHP's default. I've changed the application name so most other classes default to the correct application name. But not this one script and so far, only for this one file.
I've included the body of the component, for review, below. You can see that the #method annotation uses the App\ domain. The trouble comes in when I use PHPStan to analyze my code. If I leave the annotation as is, PHPStan will tell me:
------ --------------------------------------------------------------------------------------------------------------------------------------------------------------
Line src/Controller/Component/CartManagerComponent.php
------ --------------------------------------------------------------------------------------------------------------------------------------------------------------
43 Property Visualize\Controller\Component\CartManagerComponent::$Controller (Visualize\Controller\AppController) does not accept App\Controller\AppController.
44 Call to method loadModel() on an unknown class App\Controller\AppController.
💡 Learn more at https://phpstan.org/user-guide/discovering-symbols
------ --------------------------------------------------------------------------------------------------------------------------------------------------------------
The file itself doesn't use the App\ domain anywhere. I'm not sure where to look for the script to figure out whats wrong. Here is the body of my component in case you see something I do not:
<?php
declare(strict_types=1);
namespace Visualize\Controller\Component;
use Authorization\Identity;
use Cake\Controller\Component;
use Cake\Log\Log;
/**
* CartManager component
*
* #method \App\Controller\AppController getController()
* #property \Visualize\Controller\AppController $Controller
* #property \Visualize\Model\Table\CartsTable $Carts
*/
class CartManagerComponent extends Component
{
/**
* Default configuration.
*
* #var array
*/
protected $_defaultConfig = [];
/**
* #var \Visualize\Controller\AppController
*/
protected $Controller;
/**
* #var \Visualize\Model\Table\CartsTable
*/
protected $Carts;
/**
* #param array $config The current configuration array
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->Controller = $this->getController();
$this->Controller->loadModel('Carts');
}
/**
* Returns the most recent active cart.
*
* #param \Authorization\Identity $user The User entity.
* #return array|\Cake\Datasource\EntityInterface|null
* #noinspection PhpUnnecessaryFullyQualifiedNameInspection
*/
public function getUserCart(Identity $user)
{
$cart = $this->Controller->Carts->newEmptyEntity();
if (!empty($this->Controller->Carts) && is_a($this->Controller->Carts, '\Visualize\Model\Table\CartsTable')) {
$query = $this->Controller->Carts->find('userCart', ['user_id' => $user->getIdentifier()]);
if (!$query->isEmpty()) {
$cart = $query->first();
} else {
$cart->set('user_id', $user->getIdentifier());
$this->Controller->Carts->save($cart);
}
if (is_object($cart) && is_a($cart, '\Cake\Datasource\EntityInterface')) {
$session = $this->Controller->getRequest()->getSession();
$session->write('Cart.id', $cart->id);
}
}
return $cart;
}
/**
* Abandons carts
*
* #param int $user_id The associated user ID
* #param int $cart_id The current cart ID
* #return void
*/
public function pruneCarts(int $user_id, int $cart_id): void
{
if (!empty($this->Controller->Carts) && is_a($this->Controller->Carts, '\Visualize\Model\Table\CartsTable')) {
// Find all the carts we didn't just create:
$userCarts = $this->Controller->Carts->find('all', ['fields' => ['id', 'user_id', 'cart_status']])
->where([
'id !=' => $cart_id,
'user_id' => $user_id,
'cart_status' => 'active',
]);
if (!$userCarts->isEmpty()) {
$count = 0;
foreach ($userCarts as $cart) {
if ($count < 5) {
$record = $this->Controller->Carts->newEmptyEntity();
$record = $this->Controller->Carts->patchEntity($record, $cart->toArray());
$record->set('id', $cart->id);
$record->set('cart_status', ABANDONED_CART);
if (!$this->Controller->Carts->save($record)) {
Log::alert('Error abandoning cart');
}
} else {
$this->Controller->Carts->delete($cart);
}
$count++;
}
}
}
}
}

Validating array in Laravel using custom rule with additional parameter

I'm working with Laravel 5.7 and I need to validate a phone length by using 2 inputs (prefix+number). The total digits has to be 10 always.
I'm using this custom rule for other projects which works fine:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class PhoneLength implements Rule
{
public $prefix;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct($prefix = null)
{
//
$this->prefix = $prefix;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
//
return strlen($this->prefix)+strlen($value) == 10 ? true : false;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return 'El Teléfono debe contener 10 dígitos (prefijo + número)';
}
}
In my controller I do something like
$validatedData = $request->validate([
'prefix' => 'integer|required',
'number' => ['integer','required', new PhoneLength($request->prefix)],
]);
Now I need to make use of arrays, so my new validation looks like
$validatedData = $request->validate([
'phones.*.prefix' => 'required',
'phones.*.number' => ['required', new PhoneLength('phones.*.prefix')],
]);
The above code doesn't work at all, the parameter is not being sent as expected.
How can I send an array value? Of course I need to get the values from the same array element, so if phones[0].number is under validation, the prefix phones[0].prefix is needed.
I've found this question, but I refuse to believe that is not possible to do in a "native" way:
Laravel array validation with custom rule
Thanks in advance
You could get $prefix from the request itself:
class PhoneLength implements Rule
{
public function passes($attribute, $value)
{
$index = explode('.', $attribute)[1];
$prefix = request()->input("phones.{$index}.prefix");
}
}
or pass the $request in the PhoneLength rule constructor, then use it.
The Abdeldayem Sherif's answer is good but there is a problem when the attribute has more level of nesting, for example: clients.*.phones.*.prefix. In this case, exploding and using the 1 index will cause an unexpected error. A better solution is using str_replace.
class PhoneLength implements Rule
{
public function passes($attribute, $value)
{
$prefixAttr = str_replace('.number', '.prefix', $attribute);
$prefix = request()->input($prefixAttr);
}
}
According to this https://github.com/laravel/framework/pull/18654 you can use, it will add your custom rule as dependent and replace asterix with needed indexes
Validator::extendDependent('contains', function ($attribute, $value, $parameters, $validator) {
// The $parameters passed from the validator below is ['*.provider'], when we imply that this
// custom rule is dependent the validator tends to replace the asterisks with the current
// indices as per the original attribute we're validating, so *.provider will be replaced
// with 0.provider, now we can use array_get() to get the value of the other field.
// So this custom rule validates that the attribute value contains the value of the other given
// attribute.
return str_contains($value,
array_get($validator->getData(), $parameters[0])
);
});
Validator::make(
[['email' => 'test#mail.com', 'provider' => 'mail.com']],
['*.email' => 'contains:*.provider']
)->validate();

Yii record is not inserting into DB

Below is my controller & model logic - I just started a barebones Yii installation to play around with it more.
I get no errors but don't see the new entry in the database - my db has been configured in the main.php (this works as Gii runs).
// controllers/PageController.php
class PageController extends Controller
{
public function actionSave($value='')
{
$pageObj = new Page;
$pageObj->savePage();
}
}
// models/Page.php
class Page extends CActiveRecord
{
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'page';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('title, date_updated', 'required'),
array('live', 'numerical', 'integerOnly'=>true),
array('user_id', 'length', 'max'=>10),
array('title', 'length', 'max'=>100),
array('content, date_published', 'safe'),
// The following rule is used by search().
// #todo Please remove those attributes that should not be searched.
array('id, user_id, live, title, content, date_updated, date_published', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'comments' => array(self::HAS_MANY, 'Comment', 'page_id'),
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
'files' => array(self::MANY_MANY, 'File', 'page_has_file(page_id, file_id)'),
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'user_id' => 'User',
'live' => 'Live',
'title' => 'Title',
'content' => 'Content',
'date_updated' => 'Date Updated',
'date_published' => 'Date Published',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* #return CActiveDataProvider the data provider that can return the models
* based on the search/filter conditions.
*/
public function search()
{
// #todo Please modify the following code to remove attributes that should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id,true);
$criteria->compare('user_id',$this->user_id,true);
$criteria->compare('live',$this->live);
$criteria->compare('title',$this->title,true);
$criteria->compare('content',$this->content,true);
$criteria->compare('date_updated',$this->date_updated,true);
$criteria->compare('date_published',$this->date_published,true);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return Page the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function savePage($value='')
{
$page = new page;
$model->isNewRecord = true;
$model->primaryKey = NULL;
$page->title='sample page';
$page->content='content for the sample page';
$page->save(false);
}
}
In Yii, when you want to insert into a table which has some null columns, you must put null columns in your rules as SAFE like below:
array('primaryKey','safe'),
Now, Yii knows that primaryKey is a null column. So, there would be no problem via inserting into the current model.
As a note, when you call save() method with FALSE, you are telling to your model to do not the validation on insert.
Also, the correct way to skip possible errors is to validate your model before inserting like below:
if($model->validate()){
// VALIDATE, YOU CAN CALL SAVE FUNCTION
}else{
//here you can send an error message via FLASH or you can debug what the exact error is like below:
CVarDumper::dump($model->getErrors(),5678,true);
Yii::app()->end();
}
I hope, it help
So simple... I hate Yii sometimes :-)
Had to set the save() to save(false)
$page = new page;
$page->isNewRecord = true;
$page->primaryKey = NULL;
$page->title='sample page';
$page->content='content for the sample page';
$page->save(false);
Thanks for that - I had missed out some columns.. (silly me)
Improve the function even further with the help above..
public function savePage()
{
$page = new page;
$page->isNewRecord = true;
$page->primaryKey = NULL;
$page->user_id = 1;
$page->live = 0;
$page->content='content for the sample page';
$page->date_updated = date('Y-m-d H:i:s');
$page->date_published = date('Y-m-d H:i:s');
$page->title='sample page';
if ($page->validate()) {
$page->save();
} else {
CVarDumper::dump($page->getErrors(),5678,true);
Yii::app()->end();
}
}

Sorting information from related tables - Symfony2

A. I have a table with users and a table with friends and here are their connections:
This in the User class:
/**
* #ORM\OneToMany(targetEntity="Friend", mappedBy="user")
*/
protected $friends;
public function __construct()
{
$this->friends = new ArrayCollection();
}
and this is in the Friend class:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="friends")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
I have an template, in which I render the friends of the current user just using
$friends = $user->getFriends();
But now I want to be able to sort the friends of the user. I created a repository class with this function:
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery('SELECT f FROM EMMyFriendsBundle:Friend f ORDER BY f.name ASC')
->getResult();
}
but it displays all the friends of all the users, not just the current user. Is there a way to show only the friends of the current user without using something like "WHERE friend.user_id = user.id"? If yes, please tell me how to do it.
B. Another thing I would like to ask is how to manage my controllers.
This is the action which renders the template to show all unsorted friends:
/*
* #Displays the basic information about all the friends of an user
*/
public function displayAction()
{
$user = $this->get('security.context')->getToken()->getUser();
if($user->getName() === NULL)
{
$name = $user->getUsername();
}
else
{
$name = $user->getName();
}
$this->get('session')->setFlash('hello', 'Hello, '.$name.'!');
$friends = $user->getFriends();
$cat = new Category();
$dd_form = $this->createForm(new \EM\MyFriendsBundle\Entity\ChooseCatType(), $cat);
return $this->render('EMMyFriendsBundle:Home:home.html.twig', array(
'name' => $name, 'friends' => $friends, 'user' => $user,
'dd_form' => $dd_form->createView()));
}
And I have a sortAction, but I don't know how to write it. It should be the same as the above, only the row
$friends = $user->getFriends();
should be replaced with
$friends = $em->getRepository('EMMyFriendsBundle:Friend')
->findAllOrderedByName();
but if I copy it, it's a lot code duplication and it's stupid. I thought of one controller with an if-statement, but what to have in it? Please give me some ideas! Thanks in advance!
Do this:
/**
* #ORM\OneToMany(targetEntity="Friend", mappedBy="user")
* #ORM\OrderBy({"name" = "ASC"})
*/
protected $friends;

CakePHP 2.0 - Use MySQL ENUM field with form helper to create Select Input

I've been researching a bit and I found that CakePHP's form helper doesn't interpret ENUM fields correctly, so it simply outputs a text input. I found a post that suggested to use a helper for that specific purpose. Does anybody know a better way to achieve this? Or if CakePHP devs intend to correct this some day?
Thanks for reading!
Below is one of the helper extention.
App::uses('FormHelper', 'View/Helper');
/**
* APP/View/Helper/MySqlEnumFormHelper.php
* It extends FormHelper to implement ENUM datatype of MySQL.
*
* http://blog.xao.jp/blog/cakephp/implementation-of-mysql-enum-datatype-in-formhelper/
*
* created Oct. 15, 2012
* CakePHP 2.2.3
*/
class MySqlEnumFormHelper extends FormHelper
{
public function input($fieldName, $options = array())
{
if (!isset($options['type']) && !isset($options['options'])) {
$modelKey = $this->model();
if (preg_match(
'/^enum\((.+)\)$/ui',
$this->fieldset[$modelKey]['fields'][$fieldName]['type'],
$m
)) {
$match = trim($m[1]);
$qOpen = substr($match, 0, 1);
$qClose = substr($match, -1);
$delimiter = $qOpen . ',' . $qClose;
preg_match('/^'.$qOpen.'(.+)'.$qClose.'$/u', $match, $m);
$_options = explode($delimiter, $m[1]);
$options['type'] = 'select';
$options['options'] = array_combine($_options, $_options);
}
}
return parent::input($fieldName, $options);
}
}
Cake attempts to be database agnostic and therefore this issue won't be "corrected" since it's not a bug. For example, SQL server doesn't have an exact equivalent of MySQL's ENUM field type.
I would recommend getting your possible list of enum values like so:
YourController.php
// get column type
$type = $this->Model->getColumnType('field');
// extract values in single quotes separated by comma
preg_match_all("/'(.*?)'/", $type, $enums);
// enums
var_dump($enums[1]);
Then use a select field in your view and pass the enums as options. Your current value you'll already have. How does that sound?
I am new to cakephp I found some old code and pieced together an enum select box for you enjoy
/**
* Behavior with useful functionality around models containing an enum type field
*
* Copyright (c) Debuggable, http://debuggable.com
*
*
*
* #package default
* #access public
*
* reworked by Nathanael Mallow for cakephp 2.0
*
*/
/*
*Use case:Add this (EnumerableBehavior.php) to app/Model/Behavior/
* -->in the Model add public $actsAs = array('Enumerable');
* -->in the *_controller add $enumOptions = $this->Categorie->enumOptions('Section');
* -->in the view add print $this->Form->input('{db_field_name}', array('options' => $enumOptions, 'label' => 'here'));
*
*
*/
class EnumerableBehavior extends ModelBehavior {
/**
* Fetches the enum type options for a specific field
*
* #param string $field
* #return void
* #access public
*/
function enumOptions($model, $field) {
//Cache::clear();
$cacheKey = $model->alias . '_' . $field . '_enum_options';
$options = Cache::read($cacheKey);
if (!$options) {
$sql = "SHOW COLUMNS FROM `{$model->useTable}` LIKE '{$field}'";
$enumData = $model->query($sql);
$options = false;
if (!empty($enumData)) {
$enumData = preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $enumData[0]['COLUMNS']['Type']);
$options = explode("','", $enumData);
}
Cache::write($cacheKey, $options);
}
return $options;
}
}
?>
If you want to use MySqlEnumFormHelper instead of normal and call it by $this->Form-> instead by $this->MySqlEnumFormHelper . You should add this line in your controller to alias MySqlEnumFormHelper as Form.
public $helpers = array('Form' => array(
'className' => 'MySqlEnumForm'
));
/* comments about previus answers ***
Use case:Add this (EnumerableBehavior.php) to app/Model/Behavior/
-->in the Model add public $actsAs = array('Enumerable');
-->in the action of the *_controller add $enumOptions = $this->YourModelName->enumOptions('db_field_name'); $this->set('enumOptions',$enumOptions);
-->in the view add print $this->Form->input('{db_field_name}', array('options' => $enumOptions, 'label' => 'here'));
*
*/
i think the Behaviour way it's good...but the array keys are integer
so i have modified the function like this
function enumOptions($model, $field) {
//Cache::clear();
$cacheKey = $model->alias . '_' . $field . '_enum_options';
$options = Cache::read($cacheKey);
$enumOptions = array();
if (!$options) {
$sql = "SHOW COLUMNS FROM `{$model->useTable}` LIKE '{$field}'";
$enumData = $model->query($sql);
$options = false;
if (!empty($enumData)) {
$enumData = preg_replace("/(enum|set)\('(.+?)'\)/", '\\2', $enumData[0]['COLUMNS']['Type']);
$options = explode("','", $enumData);
foreach ($options as $option) {
$enumOptions["$option"] = $option;
}
}
Cache::write($cacheKey, $enumOptions);
}
return $enumOptions;
}
in order to be able to save the right value in the db field when the form is submitted
I created a function that goes into AppController to handle this. I combined some of the information provided above.
Usage:
$enumList = getEnumValues($ModelField) where ModelField is in this format: 'Model.Field'
Function that I put in AppController:
function getEnumValues($ModelField){
// split input into Model and Fieldname
$m = explode('.', $ModelField);
if ($m[0] == $ModelField) {
return false;
} else {
(! ClassRegistry::isKeySet($m[0])) ? $this->loadModel($m[0]): false;
$type = $this->$m[0]->getColumnType($m[1]);
preg_match_all("/'(.*?)'/", $type, $enums);
foreach ($enums[1] as $value){$enumList[$value] = $value;}
return $enumList;
}
}

Resources