I am using goutte sracper to scrape the data , i m getting error like InvalidArgumentException - The current node list is empty. Below is the code which i m using
$string = $crawler->filter('div#links.results')->html();
if ( empty( $string ) )
return false;
$dom = new \DOMDocument;
$state = libxml_use_internal_errors(true);
$dom->loadHTML($string);
libxml_use_internal_errors($state);
$xp = new \DOMXPath($dom);
$divNodeList = $xp->query('//div[contains(#class, "results_links_deep")]
[contains(#class, "web-result")]
/div[contains(#class, "links_main")]
[contains(#class, "links_deep")]
[contains(#class, "result__body")]');
$results = [];
if(count($divNodeList) > 0){
foreach ($divNodeList as $divNode) {
$results[] = [
trim($xp->evaluate('string(./h2/a[#class="result__a"])', $divNode)),
trim($xp->evaluate('string(.//a[#class="result__snippet"])', $divNode)),
trim($xp->evaluate('string(.//a[#class="result__url"])', $divNode))
];
}
}
I tried using the solution as below
if ($crawler->filter('div#links.results')->count() > 0 ) {
$string = $crawler->filter('div#links.results')->html()
}
then it started giving another error like DOMDocument::loadHTML(): Empty string supplied as input
Any suggestions please ?
Your filterdid not return any results. That is why it crashed. That's how I solved this issue, by adding a try catch.
try {
$string = $crawler->filter('div#links.results')->html()
} catch (\InvalidArgumentException $e) {
// Handle the current node list is empty..
}
Related
I'm trying to achieve a dynamic query with DQL in doctrine. I've checked several post about this subject but all the solutions are static. I want to achieve somethin like this:
$qb->where(
$qb->expr()->orX(
$qb->expr()->like('e.cliente', ':cliente_tag'),
$qb->expr()->like('e.cliente', ':cliente_tag2'),
$qb->expr()->like('e.cliente', ':cliente_tag3')
),
$qb->expr()->orX(
$qb->expr()->like('e.apoderado', ':apoderado_tag'),
$qb->expr()->like('e.apoderado', ':apoderado_tag2'),
$qb->expr()->like('e.apoderado', ':apoderado_tag3')
)
);
but inside a loop like this:
foreach ($options['camposTexto'] as $i => $campoTexto) {
switch ($campoTexto['appliedTo']) {
case 'apoderado': {
$exp = [];
foreach ($campoTexto['tags'] as $tag) {
$exp[] = $qb->expr()->like('e.apoderado', ':apoderado_tag' . $i);
$parameters['apoderado_tag' . $i] = '%' . $tag . '%';
}
if ($isFirst) {
$isFirst = false;
$qb->where($qb->expr()->orX($exp));
} else {
$qb->andWhere($qb->expr()->orX($exp));
}
break;
}
case 'cliente': {
$exp = [];
foreach ($campoTexto['tags'] as $tag) {
$expresiones[] = $qb->expr()->like('e.cliente', ':cliente_tag' . $i);
$parameters['cliente_tag' . $i] = '%' . $tag . '%';
}
if ($isFirst) {
$isFirst = false;
$qb->where($qb->expr()->orX($exp));
} else {
$qb->andWhere($qb->expr()->orX($exp));
}
break;
}
}
}
tags is an array of strings. As you see I passed the array of expresions but doctrine throws me an Exception.
So far so now I have not found any solution to my problem.
Any Idea?
Thanks in advance!
Looking at this post I figured out the solution. It would be something like this:
case 'apoderado': {
$orX = $qb->expr()->orX();
foreach ($campoTexto['tags'] as $y => $tag) {
$orX->add($qb->expr()->like('e.apoderado', $qb->expr()->literal('%' . $tag . '%'))); //<= with literal because I can't set the parameters later in the qb
}
$expresiones[] = $orX;
break;
}
after all the case/break
$andX = $qb->expr()->andX();
$qb->where($andX->addMultiple($expresiones));
return $qb->getQuery()->getResult();
I got this error in my error log
PHP Warning: Invalid argument supplied for foreach()
foreach ( $fields as $field ) {
if ( $field['name'] == $fieldname ) {
$characteristics = $field;
}
}
Does anyone know how to fix this?
Thanks
one problem is if your variable is empty and inside a foreach loop the warning appears
Try this
if (is_array($fields) || is_object($fields)) {
foreach ($fields as $field) {
if ($field['name'] == $fieldname) {
$characteristics = $field;
}
}
}
I'm searching for a way to create a custom action button which allows me to make a new DataObject with pre-filled content from another DataObject. As a simple example: When I have an email and click the "answer"-button in my email-client, I get a new window with pre-filled content from the email before. I need exactly this functionality for my button. This button should appear next to each DataObject in the GridField.
So I know how to make a button and add it to my GridField (--> https://docs.silverstripe.org/en/3.2/developer_guides/forms/how_tos/create_a_gridfield_actionprovider/) and I know how to go to a new DataObject:
Controller::curr()->redirect($gridField->Link('item/new'));
I also found out that there is a duplicate function for DataObjects:
public function duplicate($doWrite = true) {
$className = $this->class;
$clone = new $className( $this->toMap(), false, $this->model );
$clone->ID = 0;
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
if($doWrite) {
$clone->write();
$this->duplicateManyManyRelations($this, $clone);
}
$clone->invokeWithExtensions('onAfterDuplicate', $this, $doWrite);
return $clone;
}
Perhaps it's easier than I think but at the moment I just don't get how to rewrite this to get what I need. Can somebody give me a hint?
That's for sure not the cleanest solution but I think it should do the trick.
At first let's create the custom gridfield action. Here we will save all accessible records in a session and add a query string to the url so that we'll know which object we want to "clone"
public function getColumnContent($gridField, $record, $columnName) {
if(!$record->canEdit()) return;
$field = GridField_FormAction::create(
$gridField,
'clone'.$record->ID,
'Clone',
'clone',
array('RecordID' => $record->ID)
);
$values = Session::get('ClonedData');
$data = $record->data()->toMap();
if($arr = $values) {
$arr[$record->ID] = $data;
} else {
$arr = array(
$record->ID => $data
);
}
Session::set('ClonedData', $arr);
return $field->Field();
}
public function getActions($gridField) {
return array('clone');
}
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
if($actionName == 'clone') {
$id = $arguments['RecordID'];
Controller::curr()->redirect($gridField->Link("item/new/?cloneID=$id"));
}
}
after adding this new component to our gridfield,
$gridField->getConfig()->addComponent(new GridFieldCustomAction());
we'll need to bring the data into the new form. To do so, add this code directly above "return $fields" on your getCMSFields function so it will be executed every time we'll open this kind of object.
$values = Session::get('ClonedData');
if($values) {
Session::clear('ClonedData');
$json = json_encode($values);
$fields->push(LiteralField::create('ClonedData', "<div id='cloned-data' style='display:none;'>$json</div>"));
}
At the end we need to bring the content back into the fields. We'll do that with a little bit of javascript so at first you need to create a new script.js file and include it in the ss backend (or just use an existing one).
(function($) {
$('#cloned-data').entwine({
onmatch: function() {
var data = JSON.parse($(this).text()),
id = getParameterByName('cloneID');
if(id && data) {
var obj = data[id];
if(obj) {
$.each(obj, function(i, val) {
$('[name=' + i + ']').val(val);
});
}
}
}
});
// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript#answer-901144
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
})(jQuery);
And that's it ... quite tricky. Hope it will solve your problem.
Apparently the GridFieldExportButton only exports the currently visible data-set (paginated). Is there a way to make it export all the rows from a model?
Or alternatively: Is there a way to show all rows (eg. bypass pagination), so that the user can perform an export after showing all the rows? I don't want to show all rows all the time (which would probably be possible by setting ModelAdmin::set_page_length(<ridiculouslyHighNumber>);) but only on demand.
You can override ModelAdmin::getExportFields() to define the columns you want to export.
The method needs to return an array with column name as the key, and the db field as the value.
For example:
class MyCustomModelAdmin extends ModelAdmin {
....
public function getExportFields() {
return array(
'FirstName' => 'FirstName',
'Surname' => 'Surname',
'Age' => 'Age'
);
}
}
Solved it by creating a custom subclass of the GridFieldExportButton and using this for my models. The key is to use $gridField->getList(); instead of $gridField->getManipulatedList(); in the generateExportFileData method.
Here's the complete class for anybody interested:
class GridFieldExportAllButton extends GridFieldExportButton {
/**
* Generate export fields for CSV.
*
* #param GridField $gridField
* #return array
*/
public function generateExportFileData($gridField) {
$separator = $this->csvSeparator;
$csvColumns = ($this->exportColumns)
? $this->exportColumns
: singleton($gridField->getModelClass())->summaryFields();
$fileData = '';
$columnData = array();
$fieldItems = new ArrayList();
if($this->csvHasHeader) {
$headers = array();
// determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
// source name as the header instead
foreach($csvColumns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}
$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";
}
$items = $gridField->getList();
foreach($items as $item) {
$columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) {
if(!is_string($columnHeader) && is_callable($columnHeader)) {
if($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '\"', $value) . '"';
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
$item->destroy();
}
return $fileData;
}
}
Thanks for this!
I had to use this for Members GF in Security Admin.
Created an extension for anyone interested.
class SecurityAdminExtension extends Extension{
function updateEditForm($form){
$gf = $form->Fields()->fieldByName('Root.Users.Members');
$gfConfig = $gf->getConfig();
$gfConfig->removeComponentsByType('GridFieldExportButton');
$gfConfig->addComponent(new GridFieldExportAllButton());
}
}
I while back, I created a little plugin to make it easy to export DataObjects to CSV or Excel files.
https://github.com/firebrandhq/excel-export
It comes with a button you can add to a grid field.
It's got a dependency on PHP-Excel.
I am using Stof's DoctrineExtension bundle to retrieve my Tree, now I want to convert that tree to an array (which will then in turn get converted to json).
The format of NestedTreeRepository->childrenHierarchy() is not in the correct format though, I want to modify the output so only the node "title" property and the "id" property is returned, and put any children in a "children" subarray. In compliance with this format (JSON):
{
label: 'node1',
children: [
{ label: 'child1' },
{ label: 'child2' }
]
},
{
label: 'node2',
children: [
{ label: 'child3' }
]
}
}
I have tried to following code, this returns the same as childrenHierarchy() but would allow me to modify the query.
$query = $em
->createQueryBuilder()
->select('node')
->from('MyBundle:Page', 'node')
->orderBy('node.root, node.lft', 'ASC')
->getQuery()
;
$nodes = $query->getArrayResult();
[Do magic here]
$tree = $pagerepo->buildTree($nodes);
Is it possible to typecast every node into a much simpler object containing only the following property's:
id
title
a few other ints used for positioning
if I would then run that through json_encode() I would have exactly what I needed.
Any other solutions are of course welcome.
my code for this purpose (just made this a few hours ago)
it's a remake of stof's buildTreeArray function
in the controller (I'm writing this for symfony2):
function gettreeAction {
$query = .... // do your query
$tree = $this->buildTree($query->getArrayResult());
$response = new Response(json_encode($tree));
return $response;
}
private function buildTree($nodes)
{
$nestedTree = array();
$l = 0;
if (count($nodes) > 0) {
// Node Stack. Used to help building the hierarchy
$stack = array();
foreach ($nodes as $child) {
$item = array();
$item['name'] = $child['title'];
$item['id'] = 'page_'.$child['id'];
$item['level'] = $child['level'];
$item['children'] = array();
// Number of stack items
$l = count($stack);
// Check if we're dealing with different levels
while($l > 0 && $stack[$l - 1]['level'] >= $item['level']) {
array_pop($stack);
$l--;
}
// Stack is empty (we are inspecting the root)
if ($l == 0) {
// Assigning the root child
$i = count($nestedTree);
$nestedTree[$i] = $item;
$stack[] = &$nestedTree[$i];
} else {
// Add child to parent
$i = count($stack[$l - 1]['children']);
$stack[$l - 1]['children'][$i] = $item;
$stack[] = &$stack[$l - 1]['children'][$i];
}
}
}
return $nestedTree;
}
works perfectly with jqTree...
I have solved it as following:
public function getPageTreeAction() {
$pagerepo = $this->getDoctrine()->getRepository('MyBundle:Page');
$em = $this->getDoctrine()->getEntityManager();
$query = $em
->createQueryBuilder()
->select('node')
->from('MyCorpBundle:Page', 'node')
->orderBy('node.root, node.lft', 'ASC')
->getQuery();
$flatnodearray = $query->getArrayResult();
$flatsimplenodearray = array();
foreach ($flatnodearray as $currentNode) {
$currentSimpleNode = array();
$currentSimpleNode['id'] = $currentNode['id'];
$currentSimpleNode['lft'] =$currentNode['lft'];
$currentSimpleNode['rgt'] = $currentNode['rgt'];
$currentSimpleNode['lvl'] = $currentNode['lvl'];
$currentSimpleNode['title'] = $currentNode['title'];
$flatsimplenodearray[] = $currentSimpleNode;
}
$tree = $pagerepo->buildTree($flatsimplenodearray);
$response = new Response(json_encode($tree));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
I would use the Stofs Repository function to get the nodes in an hierarchical array:
$repo = $em->getRepository('MyBundle:Page');
$arrayTree = $repo->childrenHierarchy();
And I think there is no other solution than modify that array manually. After you have removed some properties that you dont need, you can json_encode the array and return it.