With Behat how can I make sure that a select field contains a given set of options?
I can't see any core methods for checking this.
Add the following to your FeatureContext.php:
/**
* #Then /^the select field "([^"]*)" should have a list containing:$/
*
* #param $locator
* string $locator input id, name or label
* #param \Behat\Gherkin\Node\PyStringNode $list
* A list of options that should be present.
*/
public function shouldHaveAListContaining($locator, PyStringNode $list): void {
$session = $this->getSession();
$page = $session->getPage();
$element = $page->findField($locator);
if ($element === NULL) {
throw new \InvalidArgumentException(sprintf('Could find element "%s".', $locator));
}
$options = [];
foreach ($element->findAll('css', 'option') as $option) {
$options[] = $option->getText();
}
$missing = array_diff($list->getStrings(), $options);
if (count($missing) > 0) {
$context = [$locator, implode(', ', $missing)];
throw new \RuntimeException(vsprintf('Element "%s" is missing these options "%s"', $context));
}
}
And the call it like this:
And the select field "YOUR_LABEL" should have a list containing:
"""
Option 1
Option 2
"""
Related
When I try to get all titles of spreadsheets from Google drive, I got this message The string could not be parsed as XML:
ERROR -> SimpleXMLElement::__construct(): Entity: line 1: parser error : Start tag expected, '<' not found in C:\....
ERROR-> SimpleXMLElement::__construct(): { in C:\....
ERROR-> SimpleXMLElement::__construct(): ^ in C:\...
I try to connect on Gooogle drive, which I success, but I need to get the names of spreadsheets on Google drive, I previously use API v3 now I need to use V4.
this is code for call method getSpreadsheets();
$serviceRequest = new DefaultServiceRequest($token->access_token, $token->token_type);
ServiceRequestFactory::setInstance($serviceRequest);
$spreadsheetService = new Google\Spreadsheet\SpreadsheetService();
$spreadsheetFeed = $spreadsheetService->getSpreadsheets();
This is the code of method getSpreadsheets:
public function getSpreadsheets()
{
return new SpreadsheetFeed(
ServiceRequestFactory::getInstance()->get('v4/spreadsheets/1u7WYzJOYMX7uH3AIM70yVLOaHBy8p_uifuJe_Saa2T4?fields=sheets.properties.title')
);
}
And this is SpreadsheetFeed class, where is error causes:
namespace Google\Spreadsheet;
use ArrayIterator;
use SimpleXMLElement;
/**
* Spreadsheet feed.
*
* #package Google
* #subpackage Spreadsheet
* #author Asim Liaquat <asimlqt22#gmail.com>
*/
class SpreadsheetFeed extends ArrayIterator
{
/**
* The spreadsheet feed xml object
*
* #var \SimpleXMLElement
*/
protected $xml;
/**
* Initializes the the spreadsheet feed object
*
* #param string $xml the raw xml string of a spreadsheet feed
*/
public function __construct($xml)
{
$this->xml = new SimpleXMLElement($xml);
$spreadsheets = array();
foreach ($this->xml->entry as $entry) {
$spreadsheets[] = new Spreadsheet($entry);
}
parent::__construct($spreadsheets);
}
/**
* Gets a spreadhseet from the feed by its title. i.e. the name of
* the spreadsheet in google drive. This method will return only the
* first spreadsheet found with the specified title.
*
* #param string $title
*
* #return \Google\Spreadsheet\Spreadsheet|null
*/
public function getByTitle($title)
{
foreach($this->xml->entry as $entry) {
if($entry->title->__toString() == $title) {
return new Spreadsheet($entry);
}
}
return null;
}
}
I try to find a solution how to pass this error!
Any Help, how to solve this error?
Thanks!
This is solution for my app, migrate from v3 to v4 spreadsheets API:
$this->service_drive = new Google_Service_Drive($this->client);
$optParams = array('q'=> 'mimeType="application/vnd.google-apps.spreadsheet"');
$this->files = $this->service_drive->files->listFiles($optParams);
}
// $this->service_drive = new Google_Service_Drive($this->client);
//$optParams = array('q'=> 'mimeType="application/vnd.google-apps.spreadsheet"');
//$this->files = $this->service_drive->files->listFiles($optParams);
//$gdata_spreadsheets=$this->files;//my test
if($this->files !==null){
if (count($this->files->getFiles()) == 0) {
$gdata_spreadsheets=array("No Spreadsheets");
} else {
foreach ($this->files->getFiles() as $file) {
$gdata_spreadsheets[$file->getId()]=$file->getName();
}
}
}
//trenuto ispod code rjesavam
if($gdata->spreadsheet_id != '' && isset( $gdata_spreadsheets[$gdata->spreadsheet_id] ) && $this->files !== null){
$this->service = new Google_Service_Sheets($this->client);
$this->response = $this->service->spreadsheets->get($gdata->spreadsheet_id);
$this->sheets=$this->response->getSheets();
// $spreadsheet = $this->service_drive($gdata_spreadsheets[$gdata->spreadsheet_id]);
// $worksheetFeed = $spreadsheet->getWorksheets();
foreach ( $this->sheets as $sheet ){
$gdata_worksheets[$sheet->properties->sheetId]= $sheet->properties->title;
//$gdata_worksheets[]=$this->sheets;
}
//$gdata_worksheets[]=$gdata->worksheet_id;
if($gdata->worksheet_id != '' && isset( $gdata_worksheets[$gdata->worksheet_id] )){
$range = 'List 2!A1:Z';
$worksheet = $this->service->spreadsheets_values->get($gdata->spreadsheet_id, $range);
$cellFeed = $worksheet->getValues();
foreach( $cellFeed As $row) {
//$row = $cellEntry->getRow();
//$col = $cellEntry;
// $gdata_worksheets=$this->service;
if( $row > 1 ){
$gdata_columns=$row;
break;
}
}
}
}
} catch(Exception $e){
$error = $e->getMessage();
}
This is one part of my code, where is described how to migrate from v3 to v4 spreadsheets API.
I am using Prestashop 1.7 and I need to change the city input field.
I have a billing software that only allows me to use predefined cities. So I have created a table ps_cities with the entries (id an city name).
I know how to write a dropdown or a autocomplete script, but I do not know where to change the input type in the Prestashop files.
On the 1.6 version you have the input field in a theme file, but somehow I fail to find in the new version.
In PrestaShop 1.7.7.X I've created a module that includes some new (and cool!) hooks like showing below. I consider this one a good option because it will be easier to maintain in the next PrestaShop releases.
Some assumptions here: I created a relationship model CityAddress with two fields id_city and id_address and a City model with fields like name, id_state, id_country, also I continued using Address::city string name for compatibility.
/**
* #see /classes/form/CustomerAddressFormatter.php#L156
* #param array $param [
* #var array $fields
* ]
*/
public function hookAdditionalCustomerAddressFields($params)
{
($params['fields']['city'])->setType('hidden');
// New field
$formField = $params['fields'];
$formField = (new FormField())
->setName('id_city')
->setLabel($this->l('City'))
->setRequired(true)
->setType('select')
;
// If an address already exits, select the default city
if (Tools::getIsset('id_address')) {
$address = new Address(Tools::getValue('id_address'));
if (!empty($address->id_state)) {
$cities = City::getCitiesByIdState((int) $address->id_state);
if (!empty($cities)) {
foreach ($cities as $city) {
$formField->addAvailableValue(
$city['id_city'],
$city['name']
);
}
$id_city = CityAddress::getIdCityByIdAddress((int) $address->id);
$formField->setValue($id_city);
}
}
}
// Add the id_city field in the position of the city field
$keys = array_keys($params['fields']);
$search = 'city';
foreach ($keys as $key => $value) {
if ($value == $search) {
break;
}
}
$part1 = array_slice($params['fields'], 0, $key + 1);
$part2 = array_slice($params['fields'], $key + 1);
$part1['id_city'] = $formField;
$params['fields'] = array_merge($part1, $part2);
}
This one to validate the field:
/**
* #see /classes/form/CustomerAddressForm.php#L123
* #param array $param [
* #var CustomerAddressForm $form
* ]
*/
public function hookActionValidateCustomerAddressForm($params)
{
if (empty(Tools::getValue('id_city'))
|| empty(Tools::getValue('city'))) {
return false;
}
$form = $params['form'];
$idCityField = $form->getField('id_city');
$idCity = (int) Tools::getValue('id_city');
$cityObj = new City($idCity);
$city = pSQL(Tools::getValue('city'));
if ($cityObj->name !== $city) {
$idCityField->addError(sprintf(
$this->l('Invalid name in field id_city %s and city %s'),
$cityObj->name,
$city
));
return false;
}
return true;
}
And the submitted field:
/**
* #see /classes/form/CustomerAddressForm.php#L153
* #param array $param [
* #var Address $address
* ]
*/
public function hookActionSubmitCustomerAddressForm($params)
{
/** #var Address */
$address = $params['address'];
$address->save();
if (!Validate::isLoadedObject($address)) {
throw new PrestaShopException($this->l('Address object error while trying to save city'));
}
// If address has a previous value then update it
$cityAddress = CityAddress::getCityAddressByIdAddress((int) $address->id);
$city = City::getCityByNameAndIdState($address->city, $address->id_state);
$cityAddress->id_city = $city->id;
$cityAddress->id_address = $address->id;
$cityAddress->save();
}
It is possible if you have this line in the additionalCustomerAddressFields hook:
https://github.com/PrestaShop/PrestaShop/blob/develop/classes/form/CustomerAddressFormatter.php#L150
For previous version I included ['fields' => &$format] as a parameter.
You can find all form fields of the Front Office in your theme's /templates/_partials/form-fields.tpl file
My widget is:-
$this->widget('bootstrap.widgets.TbGridView',array(
'type'=>'striped bordered condensed',
'id'=>'accounts-grid',
'dataProvider'=>$model->search(),
'columns'=>array(
array(
'name'=>'id',
'header'=>'ID',
'type'=>'raw',
'value'=>'$data->id',
'htmlOptions'=>array('width'=>'40'),
'url'=>'Yii::app()->createUrl("/meetings/view", array("id"=>$data["id"]))',
),
'name',
'phone',
'mobile',
'type',
'account_manager',
'status',
array(
'class'=>'bootstrap.widgets.TbButtonColumn',
),
),
));
And I need this type of pagination for this widget.
Please help me if you have any ideas.
Create a php file CustomPager.php and put it into components folder
<?php
class CustomPager extends CLinkPager {
/**
* #var string the text shown before page buttons. Defaults to ''.
*/
public $header = '';
/**
* #var string the URL of the CSS file used by this pager.
* Defaults to false, meaning that no CSS will be included.
*/
public $cssFile = false;
/**
* #var boolean whether to display the first and last items.
*/
public $displayFirstAndLast = false;
/**
* Initializes the pager by setting some default property values.
*/
public function init() {
if ($this->nextPageLabel === null)
$this->nextPageLabel = Yii::t('bootstrap', 'Next') . ' →';
if ($this->prevPageLabel === null)
$this->prevPageLabel = '← ' . Yii::t('bootstrap', 'Previous');
if ($this->firstPageLabel === null)
$this->firstPageLabel = Yii::t('bootstrap', 'First');
if ($this->lastPageLabel === null)
$this->lastPageLabel = Yii::t('bootstrap', 'Last');
if (!isset($this->htmlOptions['class']))
$this->htmlOptions['class'] = ''; // would default to yiiPager
parent::init();
}
/**
* Creates the page buttons.
* #return array a list of page buttons (in HTML code).
*/
protected function createPageButtons() {
$totalpages = $this->getPageCount();
if (($pageCount = $this->getPageCount()) <= 1)
return array();
list ($beginPage, $endPage) = $this->getPageRange();
$currentPage = $this->getCurrentPage(false); // currentPage is calculated in getPageRange()
$buttons = array();
/**
* first page
*/
if ($this->displayFirstAndLast)
$buttons[] = $this->createPageButton($this->firstPageLabel, 0, 'first', $currentPage <= 0, false);
/**
* previous page
*/
if (($page = $currentPage - 1) < 0)
$page = 0;
$buttons[] = $this->createPageButton($this->prevPageLabel, $page, 'previous', $currentPage <= 0, false);
/**
* internal page
*/
$buttons[] = '<li class=""><a herf="">Current Page: '.($currentPage+1).' of ' . $totalpages .' </a><li>';
/**
* next page
*/
if (($page = $currentPage + 1) >= $pageCount - 1)
$page = $pageCount - 1;
$buttons[] = $this->createPageButton($this->nextPageLabel, $page, 'next', $currentPage >= ($pageCount - 1), false);
/**
* last page
*/
if ($this->displayFirstAndLast)
$buttons[] = $this->createPageButton($this->lastPageLabel, $pageCount - 1, 'last', $currentPage >= ($pageCount - 1), false);
return $buttons;
}
/**
* Creates a page button.
* You may override this method to customize the page buttons.
* #param string $label the text label for the button
* #param integer $page the page number
* #param string $class the CSS class for the page button. This could be 'page', 'first', 'last', 'next' or 'previous'.
* #param boolean $hidden whether this page button is visible
* #param boolean $selected whether this page button is selected
* #return string the generated button
*/
protected function createPageButton($label, $page, $class, $hidden, $selected) {
if ($hidden || $selected)
$class .= ' ' . ($hidden ? 'disabled' : 'active');
return CHtml::tag('li', array('class' => $class), CHtml::link($label, $this->createPageUrl($page)));
}
}
?>
and call class from widget grid view
'pager'=>array(
'class'=>'CustomPager',
),
$tagged = Os :: model()-> withTags("windows, windows7, windowsXp")-> find();
I want to retrieve the records that are tagged with any of the following
windows, windows7, windowsXp.
By default the tags are generating a condition which are AND-ed. I want to use the OR operator for the tags. So if the record contains windows, windows7 but not windowsXp it won't be retrieved.
I've managed to find a workaround, by editing the getFindByTagsCriteria() in the ETaggableBehavior.php that comes in the extension folder.
/**
* Get criteria to limit query by tags.
* #access private
* #param array $tags
* #return CDbCriteria
*/
protected function getFindByTagsCriteria($tags) {
$criteria = new CDbCriteria();
$pk = $this->getOwner()->tableSchema->primaryKey;
if(!empty($tags)){
$conn = $this->getConnection();
$criteria->select = 't.*';
if(count($tags) >0){
$criteria -> join .= "
JOIN {$this->getTagBindingTableName()} bt
ON t.{$pk} = bt.{$this->getModelTableFkName()}
JOIN {$this->tagTable} tag0
ON tag0.{$this->tagTablePk} = bt.{$this->tagBindingTableTagId} AND (";
for($i = 0, $count = count($tags); $i < $count; $i++){
$tag = $conn->quoteValue($tags[$i]);
$criteria->join .= " tag0.`{$this->tagTableName}` = $tag OR";
}
$criteria -> join = rtrim($criteria -> join, "OR");
$criteria -> join .= ")";
}
}
if($this->getScopeCriteria()){
$criteria->mergeWith($this->getScopeCriteria());
}
return $criteria;
}
I would really appreciate any other way without having to modify the plugin itself.
What I'd do here is set the withTags() method in your model to take an array value, for example something like this:
/**
* #param array $tags List of tags to search for
* #return named scope
*/
public function withTags($tags)
{
$condition = '1';
$params = array();
foreach($tags as $key=>$value)
{
$condition.=' OR tag = :tag'.$key;
$params[':tag'.$key] = $value;
}
$this->getDbCriteria()->mergeWith(array(
'condition'=>$condition,
'params'=>$params,
));
return $this;
}
This way you should be able to call your named scope like so:
$tags = array('windows', 'windows7', 'windowsXp'),
$tagged = Os::model()->withTags($tags)->findAll();
Sam from Yii development team helped me solve this by adding two more functions to the ETaggableBehavior.php
/**
* Get criteria to limit query to match any of tags specified
* #access private
* #param array $tags
* #return CDbCriteria
*/
protected function getFindByAnyTagsCriteria($tags) {
$criteria = new CDbCriteria();
$pk = $this->getOwner()->tableSchema->primaryKey;
if(!empty($tags)){
$conn = $this->getConnection();
foreach($tags as &$tag) {
$tag = $conn->quoteValue($tag);
}
unset($tag);
$tags = implode(', ', $tags);
$criteria->select = 't.*';
$criteria->join .=
"JOIN {$this->getTagBindingTableName()} bt ON t.{$pk} = bt.{$this->getModelTableFkName()}
JOIN {$this->tagTable} tag ON tag.{$this->tagTablePk} = bt.{$this->tagBindingTableTagId} AND tag.`{$this->tagTableName}` IN ($tags)";
}
}
if($this->getScopeCriteria()){
$criteria->mergeWith($this->getScopeCriteria());
}
return $criteria;
}
/**
* Limit current AR query to have any of tags specified.
* #param string|array $tags
* #return CActiveRecord
*/
public function taggedWithAnyOf($tags) {
$tags = $this->toTagsArray($tags);
if(!empty($tags)){
$criteria = $this->getFindByAnyTagsCriteria($tags);
$this->getOwner()->getDbCriteria()->mergeWith($criteria);
}
return $this->getOwner();
}
I am trying to get this code to show only fields that have values, any fields that don't have values are not meant to be displayed. It doesnt seem to be working
Any idea what I am doing wrong?
My simple test form is here http://www.healthybrighton.co.uk/wse/node/1844
/**
* Build a table of submitted values
*
* #param $form_vals array Submitted form data
* #param $select_mapping array Map select components to their value|label chocies
* #return HTML of the themed table
*/
function _format_form_state($form_vals = array(), $select_mapping) {
$output = '';
$header = array();
$rows = array();
if (!empty($form_vals)) {
foreach ($form_vals as $component_name => $component_value) {
$rows = array_merge(
$rows,
_add_component_row(
$component_name,
$component_value,
0,
$select_mapping
)
);
}
}
$output .= theme('table', $header, $rows);
return $output;
}
/**
* Build a table of submitted values
*
* #param $select_mapping array Map select components to their value|label chocies
* #param $values array Submitted form data
* #return HTML of the themed table
*/
function _format_form_state($select_mapping, $values = array()) {
$header = array(t('First'), t('Second'), t('Third'), t('Fourth'));
$rows = array();
foreach ($values as $cname => $cval) {
$rows[] = array($cname, $cval, 0, $select_mapping);
}
return theme_table($header, $rows);
}
$select_mapping should be first argument in function.
Argument with default value should not be preceded by argument without a default value.