Symfony + DomCrawler - how to extract data attributes from a <div> - symfony

I'm using Symfony 2.8 & DomCrawler to parse a web site and I'm having a problem reading data attributes from a HTML entity. It might be as simple as a specific convention for data attributes, but I've not been able to find any references or examples on the web that discuss how to retrieve data attributes via DomCrawler.
Here are the details:
I have encountered an instance of this construct in the HTML I am parsing (from another web site, so I can't modify this HTML):
<div class='slideshowclass' id='slideshow'>
<div data-thumb='http://www.example.com/thumbs/1.jpg'
data-src='http://www.example.com/thumbs/1.jpg'></div>
<div data-thumb='http://www.example.com/thumbs/2.jpg'
data-src='http://www.example.com/thumbs/2.jpg'></div>
<div data-thumb='http://www.example.com/thumbs/3.jpg'
data-src='http://www.example.com/thumbs/3.jpg'></div>
<div data-thumb='http://www.example.com/thumbs/4.jpg'
data-src='http://www.example.com/thumbs/4.jpg'></div>
<div data-thumb='http://www.example.com/thumbs/5.jpg'
data-src='http://www.example.com/thumbs/5.jpg'></div>
<div data-thumb='http://www.example.com/thumbs/6.jpg'
data-src='http://www.example.com/6.jpg'></div>
</div>
I'm using this code to search the block of div's and return the data-src values:
function getList( Crawler $pWebDoc ) {
$list = $pWebDoc->filter( 'div#slideshow');
if ( !$list )
return null;
$retlist = null;
$x = $list->count();
if ( $x > 0 ) {
/* #var $item Crawler */
$retlist = $list->children()->each( function (Crawler $item, $i ) {
return ( "$i:" . $item->attr( 'data-src' ));
});
}
return ( $retlist );
}
From the DomCrawler docs I expect the attr function to return the data-src attribute value, but it returns null; the return from my function being an array of 6 elements with just the number and not additional text.
Thanks in advance for your help.

This can be easily done using the DOMDocument and XPath libraries. XPath does provide the capability of returning array's of values instead of nodes.
/**
* Filters the list of nodes with an XPath expression.
*
* The XPath expression should already be processed to apply it in the context of each node.
*
* #param string $xpath
*
* #return Crawler
*/
private function filterRelativeXPath($xpath)
{
$prefixes = $this->findNamespacePrefixes($xpath);
$crawler = $this->createSubCrawler(null);
foreach ($this->nodes as $node) {
$domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes);
$crawler->add($domxpath->query($xpath, $node));
}
return $crawler;
}
This function is from Crawler.php. My experience has been that the Crawler wasn't happy with complex xpath expressions, which resulted in switching from the DomCrawler to using xpath / dom directly.
Your base xpath query would be like //div/#data-src

Related

Read values of entity reference within another entity reference in Paragraph preprocess - D9

I have a content type that has a Paragraph entity reference field named "Grants". This "Grants" paragraph field has another paragraph entity reference field names "contacts". So the entity references are nested as :
Awards content type ---->(refers)--->Grants paragraph field--->(refers)--->Contacts paragraph field
I am trying to read the contacts reference fields in hook_preprocess_paragraph method and I cannot do that:
function mytheme_preprocess_paragraph(array &$variables) {
$pval = $variables['elements']['#paragraph'];
$paragraph = $pval->field_grant_contacts->getValue();
foreach ( $paragraph as $element ) {
$p = \Drupal\paragraphs\Entity\Paragraph::load( $element['target_id'] );
$text = $p->field_contact_name->getValue();
}
dd($text);
}
I am trying to reference the primary field - field_grant_contact. Through that I'm trying to read field_contact_name and the getValue method returns null. Any help on how to read nested entity reference fields?
There is a helpful method Entity::referencedEntities to get relations.
Also to get text value use $p->field_contact_name->value.
In your case try the next code:
function mytheme_preprocess_paragraph(array &$variables) {
/** #var \Drupal\paragraphs\ParagraphInterface $paragraph */
$paragraph = $variables['elements']['#paragraph'];
/** #var \Drupal\paragraphs\ParagraphInterface[] $contacts */
$contacts = $paragraph->field_grant_contacts->referencedEntities();
foreach ($contacts as $contact) {
$text = $contact->field_contact_name->value;
var_dump($text);
}
}

How to remove query string from static resource?

I've tried it with
https://www.drupal.org/project/remove_querystring_from_static_resource
But it doesn't work well for me .
How can I achieve that programmatically?
The following is the test result:
This trouble is usually encountered when static resources (eg. images, css & javascript files) are accessed using a query string.
Eg: http://example.com/image.png?something=test
Those query strings are used for avoiding browser caching. Their values are changed, so the browser should do a new request instead of getting cached resource.
You should remove those query strings (?something=test in my example) and use some suitable Cache-Control headers.
Edit:
Try this code.
Replace THEMENAME with your theme name.
/**
* Implements template_process_html().
* Remove Query Strings from CSS & JS filenames
*/
function THEMENAME_process_html( &$variables) {
$variables['styles'] = preg_replace('/\.css\?[^"]+/', '.css', $variables['styles']);
$variables['scripts'] = preg_replace('/\.js\?[^"]+/', '.js', $variables['scripts']);
}
/**
* Implement hook_image_style
* Override theme image style to remove query string.
* #param $variables
*/
function THEMENAME_image_style($variables) {
// Determine the dimensions of the styled image.
$dimensions = array(
'width' => $variables['width'],
'height' => $variables['height'],
);
image_style_transform_dimensions($variables['style_name'], $dimensions);
$variables['width'] = $dimensions['width'];
$variables['height'] = $dimensions['height'];
// Determine the URL for the styled image.
$variables['path'] = image_style_url($variables['style_name'], $variables['path']);
// Remove query string for image.
$variables['path'] = preg_replace('/\?.*/', '', $variables['path']);
return theme('image', $variables);
}
Finally I solved the issue with this code:
use Drupal\Core\Asset\AttachedAssetsInterface;
/**
* Implements hook_css_alter().
*/
function bootstrap_css_alter(&$css, AttachedAssetsInterface $assets){
foreach ($css as &$file) {
if ($file['type'] != 'external') {
$file['type'] = 'external';
$file['data'] = '/' . $file['data'];
}
}
}
/**
* Implements hook_js_alter().
*/
function bootstrap_js_alter(&$javascript, AttachedAssetsInterface $assets){
foreach ($javascript as &$file) {
if ($file['type'] != 'external') {
$file['type'] = 'external';
$file['data'] = '/' . $file['data'];
}
}
}
Put this code to your yourthemename.theme file.
it works perfect on drupal 8
Hope this help you guys.

How to show content of a custom block using PHP code format in Drupal 8

I have added some custom code in a block using PHP code format to show that block on a specific page. I have checked all the things working fine on Devel PHP page but contents are not showing on page. The code below fetches the field value of a destination node.
$refer = $_SERVER[HTTP_REFERER];
$parsed = parse_url($refer);
$alias = array_pop($parsed);
$dst = \Drupal::service('path.alias_manager')->getPathByAlias($alias , $langcode);
$nid = array_pop(explode('/', $dst));
$dest_node = node_load($nid);
$body = $dest_node->get('body')->getValue();
print $body; //have tried other printing methods also but invain
Hope this clarifies the question.
Thanks
Are you sure that it works in Devel? I've just tried to execute your code, and this line:
$body = $dest_node->get('body')->getValue();
returns Array.
Try to use this one instead:
$body = $dest_node->body->value;
First of all, your first block of code (getting current node) can be replaced with just one line:
$node = \Drupal::service('current_route_match')->getParameter('node');
And the whole block can be changed in the following way:
if ($node = \Drupal::service('current_route_match')->getParameter('node')) {
print $node->body->value;
}
P.S. And it's definitely a bad idea to use PHP text filter. You may easily write your own custom module providing required block. The simplest block plugin requires several lines of code:
/**
* #file
* Contains \Drupal\my_module\Plugin\Block\MyBlock.
*/
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides my super block.
*
* #Block(
* id = "my_module_block",
* admin_label = #Translation("My Block"),
* category = #Translation("My Module"),
* )
*/
class MyBlock extends BlockBase{
/**
* Builds and returns the renderable array for this block plugin.
*
* #return array
* A renderable array representing the content of the block.
*
* #see \Drupal\block\BlockViewBuilder
*/
public function build() {
if ($node = \Drupal::service('current_route_match')->getParameter('node')) {
return [ '#markup' => $node->body->value ];
}
}
}
This file MyBlock.php must be placed in /src/Plugin/Block/ directory inside your custom module named my_module.

Is there a way (other than sql) to get the mlid for a given nid in drupal?

I've got a node, I want it's menu. As far as I can tell, node_load doesn't include it. Obviously, it's trivial to write a query to find it based on the path node/nid, but is there a Drupal Way to do it?
if the menu tree has multiple levels sql seems a better option.
a sample for drupal 7 is given bellow where path is something like 'node/x'
function _get_mlid($path, $menu_name) {
$mlid = db_select('menu_links' , 'ml')
->condition('ml.link_path' , $path)
->condition('ml.menu_name',$menu_name)
->fields('ml' , array('mlid'))
->execute()
->fetchField();
return $mlid;
}
The Menu Node module exposes an API to do this.
You can read the documentation (Doxygen) in the code. I think the functionality you need is provided by the menu_node_get_links($nid, $router = FALSE) method:
/**
* Get the relevant menu links for a node.
* #param $nid
* The node id.
* #param $router
* Boolean flag indicating whether to attach the menu router item to the $item object.
* If set to TRUE, the router will be set as $item->menu_router.
* #return
* An array of complete menu_link objects or an empy array on failure.
*/
An associative array of mlid => menu object is returned. You probably only need the first one so it might look like something like this:
$arr = menu_node_get_links(123);
list($mlid) = array_keys($arr);
Otherwise, you can try out the suggestion in a thread in the Drupal Forums:
Use node/[nid] as the $path argument for:
function _get_mlid($path) {
$mlid = null;
$tree = menu_tree_all_data('primary-links');
foreach($tree as $item) {
if ($item['link']['link_path'] == $path) {
$mlid = $item['link']['mlid'];
break;
}
}
return $mlid;
}

OR operator in Drupal View Filters

I need to implement an OR operator between some filters in a Drupal View.
By default, Drupal AND's every filter together.
By using
hook_views_query_alter(&$view, &$query)
I can access the query ( var $query ) , and I can change either :
$query->where[0]['type']
to 'OR', or
$query->group_operator
to 'OR'
The problem is however, that I do not need OR's everywhere. I've tried changing both of them to OR seperately, and it doesn't yield the desired result.
It seems changing those values, puts OR's everywhere, while I need => ( filter 1 AND filter 2 ) OR ( filter 3 ), so just 1 OR.
I could just check the Query of the View, copy it, modify it, and run it through db_query, but that's just dirty ..
Any suggestions ?
Thx in advance.
If you are using Views 3 / Drupal 7 and looking for the answer to this question, it is baked right into Views. Where it says "add" next to filters, click the dropdown, then click "and/or; rearrange". It should be obvious from there.
Unfortunately this is still a missing feature in Views2. It has long been asked for and was promised a while ago, but seems to be a tricky piece of work and is now scheduled for Views3.
In the meantime you could try the Views Or module mentioned in that thread. As of today, it is still in dev status, but seems to be actively maintained and the issue queue does not look to bad, so you might want to give it a try.
if you want do it with view_query_alter hook, you should use $query->add_where() where you can specify if it's AND or OR. From views/include/query.inc
/**
* Add a simple WHERE clause to the query. The caller is responsible for
* ensuring that all fields are fully qualified (TABLE.FIELD) and that
* the table already exists in the query.
*
* #param $group
* The WHERE group to add these to; groups are used to create AND/OR
* sections. Groups cannot be nested. Use 0 as the default group.
* If the group does not yet exist it will be created as an AND group.
* #param $clause
* The actual clause to add. When adding a where clause it is important
* that all tables are addressed by the alias provided by add_table or
* ensure_table and that all fields are addressed by their alias wehn
* possible. Please use %d and %s for arguments.
* #param ...
* A number of arguments as used in db_query(). May be many args or one
* array full of args.
*/
function add_where($group, $clause)
I added it by concatenating the string.
It is relatively specific to the implementation - people would need to play with field to match for OR - node.title in the following code and the field to match it with - node_revisions.body in this case.
Extra piece of code to make sure that node_revisions.body is in the query.
/**
* Implementation of hook_views_api().
*/
function eventsor_views_api() { // your module name into hook_views_api
return array(
'api' => 2,
// might not need the line below, but in any case, the last arg is the name of your module
'path' => drupal_get_path('module', 'eventsor'),
);
}
/**
*
* #param string $form
* #param type $form_state
* #param type $form_id
*/
function eventsor_views_query_alter(&$view, &$query) {
switch ($view->name) {
case 'Events':
_eventsor_composite_filter($query);
break;
}
}
/**
* Add to the where clause.
* #param type $query
*/
function _eventsor_composite_filter(&$query) {
// If we see "UPPER(node.title) LIKE UPPER('%%%s%%')" - then add and to it.
if (isset($query->where)) {
$where_count = 0;
foreach ($query->where as $where) {
$clause_count = 0;
if (isset($where['clauses'])) {
foreach ($where['clauses'] as $clause) {
$search_where_clause = "UPPER(node.title) LIKE UPPER('%%%s%%')";
// node_data_field_long_description.field_long_description_value
$desirable_where_clause = "UPPER(CONCAT_WS(' ', node.title, node_revisions.body)) LIKE UPPER('%%%s%%')";
if ($clause == $search_where_clause) {
// $query->add_where('or', 'revisions.body = %s'); - outside of what we are looking for
$query->where[$where_count]['clauses'][$clause_count] = $desirable_where_clause;
// Add the field to the view, just in case.
if (!isset($query->fields['node_revisions_body'])) {
$query->fields['node_revisions_body'] = array(
'field' => 'body',
'table' => 'node_revisions',
'alias' => 'node_revisions_body'
);
}
}
$clause_count++;
}
}
$where_count++;
}
}
}

Resources