Planet Drupal

Syndicate content
Drupal.org - aggregated feeds in category Planet Drupal
Updated: 57 min 41 sec ago

Drupalize.Me: Release Day: PhpStorm for Modern PHP Development

Wed, 02/18/2015 - 15:15
div class=field field-name-body field-type-text-with-summary field-label-hidden text-content text-secondarydiv class=field-itemsdiv class=field-item evenpReady to take your PHP development to the next level? This week, we have another batch of video tutorials from the awesome folks at JetBrains on their IDE, PhpStorm. In these tutorials, you'll learn how to generate code using templates, set up your modern PHP app with namespaces, PSR-0 or PSR-4, integrate Composer, and debug like a pro./p /div/div/divdiv id=comment-wrapper-nid-2048/div

Propeople Blog: Varnish Tips and Tricks

Wed, 02/18/2015 - 06:02
div id=comment-wrapper-nid-1706/divdiv class=field field-name-body field-type-text-with-summary field-label-hiddendiv class=field-itemsdiv class=field-item even property=content:encodedp dir=ltrIn this article we would like to share some use cases and recipes for configuring Varnish./ph4 dir=ltrUse custom data for building varnish cache hash/h4p dir=ltrBy default, Varnish uses URL as a parameter for caching. In the VCL file, it is also possible to add custom data (for example: location, or custom header value) to hashing by using the sub vcl_hash{} configuration part. But there is yet another solution to be used that Varnish comes equipped with out of the box. We can set a Vary header that is respected. So, if we want our cache to be based on header X-MyCustomHeader in Drupal, then we can set the header to/pp dir=ltrVary: Cookie,Accept-Encoding,X-MyCustomHeader. This way, different values of our custom header will have different cache records in Varnish./ph4 dir=ltrLimit access to the site by ip address/h4p dir=ltrWhen we build an intranet website, we can limit access to it on the Varnish level. This can be done in following way:/pp dir=ltrFirst we define list of allowed IP addresses:/pp dir=ltr style=margin-left: 40px;acl offices {/pp dir=ltr style=margin-left: 40px;   localhost;/pp dir=ltr style=margin-left: 40px;   127.0.0.1;/pp dir=ltr style=margin-left: 40px;   1.2.3.4;`/pp dir=ltr style=margin-left: 40px;   5.6.7.8;/pp dir=ltr style=margin-left: 40px;}/pp dir=ltrThen we restrict access to non matching addresses:/pp dir=ltr style=margin-left: 40px;sub vcl_recv {/pp dir=ltr style=margin-left: 40px;   if ( req.http.host ~ (intranet\.example\.com)$ amp;amp; !(client.ip ~ offices) ) {/pp dir=ltr style=margin-left: 40px;        error 403 Access denied;/pp dir=ltr style=margin-left: 40px;   }/pp dir=ltr style=margin-left: 40px;}/ph4 dir=ltrSSL termination/h4p dir=ltrAs Varnish is not handling https traffic, we need to terminate SSL before it hits Varnish. For that we can use nginx. Here is a list of links to articles that dive deeper into this topic:/pp dir=ltra href=https://www.digitalocean.com/community/tutorials/how-to-configure-varnish-cache-4-0-with-ssl-termination-on-ubuntu-14-04https://www.digitalocean.com/community/tutorials/how-to-configure-varnish-cache-4-0-with-ssl-termination-on-ubuntu-14-04/a/pp dir=ltra href=http://edoceo.com/howto/nginx-varnish-sslhttp://edoceo.com/howto/nginx-varnish-ssl/a/pp dir=ltra href=http://mikkel.hoegh.org/2012/07/24/varnish-as-reverse-proxy-with-nginx-as-web-server-and-ssl-terminator/http://mikkel.hoegh.org/2012/07/24/varnish-as-reverse-proxy-with-nginx-as-web-server-and-ssl-terminator//a/pp dir=ltra href=https://wiki.deimos.fr/Nginx_%2B_Varnish_:_Cache_even_in_HTTPS_by_offloading_SSLhttps://wiki.deimos.fr/Nginx_%2B_Varnish_:_Cache_even_in_HTTPS_by_offloading_SSL/a/ph4 dir=ltrESI/h4p dir=ltrOn a recent Propeople project, we had the requirement to include a block with data from an external website without any caching. The tricky part was that the external site was providing XML with the data. The solution we implemented was to use ESI block pointing to the custom php file that was pulling that XML and parsing it on the fly./ph4 dir=ltrHiding js requests to external domains/h4p dir=ltrIf we need to do some CORS (a href=http://en.wikipedia.org/wiki/Cross-origin_resource_sharinghttp://en.wikipedia.org/wiki/Cross-origin_resource_sharing/a) requests instead of our Javascript doing requests directly to external domain, we can do requests to our site, but with a specific URL. Then, on the Varnish level, we can redirect that request to external domain. In this case, Varnish will act like a proxy. This can be achieved with backend options./pp dir=ltr style=margin-left: 40px;backend google {/pp dir=ltr style=margin-left: 40px; .host = 209.85.147.106;/pp dir=ltr style=margin-left: 40px; .port = 80;/pp dir=ltr style=margin-left: 40px;}/pp dir=ltr style=margin-left: 40px;sub vcl_fetch {/pp dir=ltr style=margin-left: 40px; if (req.url ~ ^/masq) {/pp dir=ltr style=margin-left: 40px;   set req.backend = google;/pp dir=ltr style=margin-left: 40px;   set req.http.host = a href=http://www.google.comquot;;www.google.com;/a/pp dir=ltr style=margin-left: 40px;   set req.url = regsub(req.url, ^/masq, );/pp dir=ltr style=margin-left: 40px;   remove req.http.Cookie;/pp dir=ltr style=margin-left: 40px;   return(deliver);/pp dir=ltr style=margin-left: 40px; }/pp dir=ltr style=margin-left: 40px;}/pp dir=ltrThis is an example from a brilliant book: a href=https://www.varnish-software.com/static/book/https://www.varnish-software.com/static/book//a/ph4 dir=ltrMultiple backends, load balancing/h4p dir=ltrIt is possible to define multiple backends for Varnish and switch between them. Most basic implementation is round robin or random. Here is an example:/pp dir=ltr style=margin-left: 40px;backend web01 {/pp dir=ltr style=margin-left: 40px;   .host = example1;/pp dir=ltr style=margin-left: 40px;   .port = 80;/pp dir=ltr style=margin-left: 40px;   .connect_timeout = 120s;/pp dir=ltr style=margin-left: 40px;   .first_byte_timeout = 300s;/pp dir=ltr style=margin-left: 40px;   .between_bytes_timeout = 60s;/pp dir=ltr style=margin-left: 40px;   .max_connections = 50;/pp dir=ltr style=margin-left: 40px;   .probe = {/pp dir=ltr style=margin-left: 40px;    .url = /;/pp dir=ltr style=margin-left: 40px;    .timeout = 10s;/pp dir=ltr style=margin-left: 40px;    .interval = 20s;/pp dir=ltr style=margin-left: 40px;    .window = 5;/pp dir=ltr style=margin-left: 40px;    .threshold = 3;/pp dir=ltr style=margin-left: 40px;   }/pp dir=ltr style=margin-left: 40px;}/pp /pp dir=ltr style=margin-left: 40px;backend web02 {/pp dir=ltr style=margin-left: 40px;   .host = example2;/pp dir=ltr style=margin-left: 40px;   .port = 80;/pp dir=ltr style=margin-left: 40px;   .max_connections = 100;/pp dir=ltr style=margin-left: 40px;   .connect_timeout = 120s;/pp dir=ltr style=margin-left: 40px;   .first_byte_timeout = 300s;/pp dir=ltr style=margin-left: 40px;   .between_bytes_timeout = 60s;/pp dir=ltr style=margin-left: 40px;   .probe = {/pp dir=ltr style=margin-left: 40px;       .url = /;/pp dir=ltr style=margin-left: 40px;       .timeout = 10s;          /pp dir=ltr style=margin-left: 40px;       .interval = 20s;       /pp dir=ltr style=margin-left: 40px;       .window = 5;/pp dir=ltr style=margin-left: 40px;       .threshold = 3;/pp dir=ltr style=margin-left: 40px;   }/pp dir=ltr style=margin-left: 40px;}/pp style=margin-left: 40px; /pp dir=ltr style=margin-left: 40px;director apache round-robin {/pp dir=ltr style=margin-left: 40px; { .backend = web01; }/pp dir=ltr style=margin-left: 40px; { .backend = web02; }/pp dir=ltr style=margin-left: 40px;}/pp style=margin-left: 40px; /pp dir=ltr style=margin-left: 40px;sub vcl_recv {/pp dir=ltr style=margin-left: 40px; set req.backend = apache;/pp dir=ltr style=margin-left: 40px;}/pp /pp dir=ltrIt is also possible to set a specific backend for visitors coming from specific IP addresses. This can have a number of helpful uses, such as making sure that the editors team has  adedicated backend server./pp dir=ltr style=margin-left: 40px;if (client.ip ~ offices) {/pp dir=ltr style=margin-left: 40px; set req.backend = web03;/pp dir=ltr style=margin-left: 40px;}/ppbr /I hope you have enjoyed our tips regarding Varnish configuration. Please feel free to share your own thoughts and tips on Varnish in the comments below!/p/div/div/divdiv class=field field-name-field-tags field-type-taxonomy-term-reference field-label-abovediv class=field-labelTags:nbsp;/divdiv class=field-itemsdiv class=field-item evena href=/tags/varnish typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Varnish/a/div/div/divdiv class=field field-name-field-service-category field-type-taxonomy-term-reference field-label-abovediv class=field-labelService category:nbsp;/divdiv class=field-itemsdiv class=field-item evena href=/services/technology typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Technology/a/div/div/divdiv class=field field-name-field-drupal-planet field-type-taxonomy-term-reference field-label-abovediv class=field-labelCheck this option to include this post in Planet Drupal aggregator:nbsp;/divdiv class=field-itemsdiv class=field-item evena href=/drupal/planet typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=planet/a/div/div/divdiv class=field field-name-field-topics field-type-taxonomy-term-reference field-label-abovediv class=field-labelTopics:nbsp;/divdiv class=field-itemsdiv class=field-item evena href=/topics/tech-development typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Tech amp; Development/a/div/div/div

Four Kitchens: Announcing SANDcamp Training for Advanced Responsive Web Design

Wed, 02/18/2015 - 00:44
div class=field-body pPatrick Coffey and I have been busy building a new version of the popular Advanced Responsive Web Design all-day training program and are excited to host it at San Diego’s span class=capsSAND/spancamp next week. Registration is open and there are several spaces remaining and we would love for you to join us!/p pResponsive Web Design is on everyone’s mind at the moment, and for good reason. The old techniques we have used to create pixel perfect sites for desktop audiences have already become a thing of the past as mobile usage accelerates./p /div div class=field-blog-categories-term-tree a href=/topics/training typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Training/a /div div class=field-blog-categories-term-tree a href=/topics/drupal-camp typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Drupal Camp/a /div div class=field-blog-categories-term-tree a href=/topics/drupal typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Drupal/a /div

Isovera Ideas Insights: When Do You Make the Move to Drupal 8?

Tue, 02/17/2015 - 21:54
div class=field field-name-field-teaser-text field-type-text-long field-label-hiddendiv class=field-itemsdiv class=field-item evenLately, whenever we start a project at Isovera, we are typically asked, quot;would you build this with Drupal 8quot;? It#039;s a good question. There are many good reasons to get a leg up with the (currently) beta release, but there are also good reasons to keep your head down and stick with Drupal 7. The official release of Drupal 8 is rapidly approaching. What might this mean to you?/div/div/div

Phase2: The Pros And Cons of Headless Drupal

Tue, 02/17/2015 - 19:38
p dir=ltrDrupal is an excellent content management system. Nodes and fields allow site administrators the ability to create complex datasets and models without having to write a singe mysql query. Unfortunately Drupal’s theming system isn#8217;t as flexible. Complete control over the dom is nearly impossible without a lot of work. Headless Drupal bridges the gap and gives us the best of both worlds./p h2 dir=ltrWhat is headless Drupal?/h2 p dir=ltrHeadless Drupal is an approach that decouples Drupal’s backend from the frontend theme. Drupal is used as a content store and admin interface. Using the services module in Drupal 7 or Core in Drupal 8, a rest web service can be created. Visitors to the site don#8217;t view a traditional Drupal theme instead they are presented with pages created with Ember.js, Angular.js, or even a custom framework. Using the web service the chosen framework can be used to transfer data from Drupal to the front end and vice versa./p h3 dir=ltrPros/h3 p dir=ltrSo what makes Headless Drupal so great? For one thing it allows frontend developers full control over the page markup. Page speed also increases since display logic is on the client side instead of the server. Sites can also become much more interactive with page transitions and animations. But most importantly the Drupal admin can also be used to power not only web apps but also mobile applications including Android and iOS./p h3Cons/h3 p dir=ltrUnfortunately Headless Drupal is not without its drawbacks. For one layout control for editors becomes much more difficult. Something that could be done easily via context or panels now requires a lot of custom fronted logic. Also if proper caching isn#8217;t utilized and the requests aren#8217;t batched properly lots of roundtrips can occur, causing things to slow down drastically./p pWant to learn more about Headless Drupal?  Check out thea href=https://groups.drupal.org/headless-drupal Headless Drupal Initiative/a on Drupal.org.  And on a related topic, check out #8220;a href=http://phase2technology.com/blog/drupal-in-a-changing-cms-landscape/Drupal 8 And The Changing CMS Landscape./a#8220;/p img src=http://track.hubspot.com/__ptq.gif?a=457100k=14bu=http%3A%2F%2Fwww.phase2technology.comr=http%3A%2F%2Fwww.phase2technology.com%2Fblog%2F9605%2Fbvt=rssp=wordpress style=float:left; xml:base=http://www.phase2technology.com/feed/ width=1 height=1 border=0 align=right/

Cameron Eagans: Use the Force!

Tue, 02/17/2015 - 18:00
pPython developers have a href=https://github.com/davidhalter/jediJedi/a. Go developers have a href=https://github.com/nsf/gocodegocode/a. Hack developers have the built-in autocomplete functionality in a href=https://github.com/facebook/hhvmhhvm/a. PHP developers have….nothing./p

Cheeky Monkey Media: How to add typekit fonts to your drupal website

Tue, 02/17/2015 - 18:00
div class=field field--name-field-article-image field--type-image field--label-hiddendiv class=field__itemsdiv class=field__item evena href=/blog/drupal-planet/how-add-typekit-fonts-your-drupal-websiteimg src=http://cheekymonkeymedia.ca/sites/default/files/typekitanddrupal.jpg width=1380 height=444 alt=How to add typekit fonts to your drupal website //a/div/div/divdiv class=field field--name-body field--type-text-with-summary field--label-hiddendiv class=field__itemsdiv class=field__item evenpSo you just got the latest design from your graphics department. Now it’s up to you, the drupal developer, to take that design and turn it into reality. The problem is that they used some fancy pants new font and you need to make sure it works on every browser and mobile device./p pThere are a few solid options to choose from, including Google fonts and the popular @font-your-face drupal module. However, one of the services that I have been using lately is Adobe Typekit. They offer thousands of fonts and make it easy to scale. Typekit offers a basic free account as well as paid...a href=/blog/drupal-planet/how-add-typekit-fonts-your-drupal-website class=more-linkRead More/a/p/div/div/div

Appnovation Technologies: Export Data From Views to CSV File

Tue, 02/17/2015 - 17:55
div class=field field-name-body field-type-text-with-summary field-label-hiddendiv class=field-itemsdiv class=field-item even property=content:encoded pIt is sometimes useful to be able to save our view results into a document to allow non-technical people to manipulate the data./p/div/div/divdiv class=field field-name-field-blog-header-image field-type-image field-label-hiddendiv class=field-itemsdiv class=field-item evena href=/blog/export-data-views-csv-fileimg typeof=foaf:Image src=http://www.appnovation.com/sites/default/files/styles/blog_listing_page_header/public/exportviewscsv.jpg?itok=uwZufoQF width=700 height=200 alt= //a/div/div/div

Tag1 Consulting: How to Maintain Contrib Modules for Drupal and Backdrop at the Same Time - Part 2

Tue, 02/17/2015 - 17:00
pThis is the second in a series of blog posts about the relationship between Drupal and a href=https://backdropcms.org/Backdrop CMS/a, a recently-released fork of Drupal. The goal of the series is to explain how a module (or theme) developer can take a Drupal project they currently maintain and support it for Backdrop as well, while keeping duplicate work to a minimum./p ul /ulpa href=http://tag1consulting.com/blog/how-maintain-contrib-modules-drupal-and-backdrop-same-time-part-2 target=_blankread more/a/p

Clemens Tolboom: Delete and edit comments on closed node

Tue, 02/17/2015 - 16:29
div class=field field-name-field-images field-type-image field-label-hiddendiv class=field-itemsdiv class=field-item evena href=/content/delete-and-edit-comments-closed-nodeimg src=http://build2be.com/sites/build2be.com/files/styles/medium/public/node-closed-comment-delete.png?itok=6fRZV3_y width=220 height=69 alt=Node closed comment delete title=Node closed comment delete //a/div/div/divdiv class=field field-name-body field-type-text-with-summary field-label-hiddendiv class=field-itemsdiv class=field-item evenpHaving a forum you needs quick deletions of improper comments./p pIn Drupal 7 and Drupal 8 you have to visit admin/content/comments to do so. But then you loose the thread./p pYou could review and use this a href=https://www.drupal.org/node/2427995patch/a or add this to your custom module. The first needs review and testing. The later needs a Drupal coder./p/div/div/div

Drupal Commerce: Using OpenID Connect for Single Sign-On with Drupal

Tue, 02/17/2015 - 16:03
div class=field field-name-body field-type-text-with-summary field-label-hiddendiv class=field-itemsdiv class=field-item even property=content:encodedpAt Commerce Guys we provide a varied range of a href=https://commerceguys.com/what-we-do/professional-servicesservices/a, including our cloud PaaS a href=https://platform.shPlatform.sh/a, this a href=/Drupal Commerce/a community website, support, and the a href=https://marketplace.commerceguys.comCommerce Marketplace/a./p pOur users may need to log in to any of these services, and sometimes several at the same time. So we needed to have a shared authentication system, a way of synchronizing user accounts, and single sign-on (SSO) functionality./p pAfter a lot of research on the existing methods, such as CAS, we found that there was no generic open-source solution which would cover all of our current needs and would also allow us to grow and scale in the future when adding new features or applications./p pWe decided to implement the OAuth 2.0 and OpenID Connect protocols, which were designed to be flexible, yet simple and standardized - exactly what we wanted./p/div/div/div

Drupal @ Penn State: Autopost to Facebook

Tue, 02/17/2015 - 15:15
div class=field field-name-body field-type-text-with-summary field-label-hiddendiv class=field-itemsdiv class=field-item even property=content:encodedpI ran into an issue with the Drupal for Facebook module, both for D6 and D7, where I wanted articles to auomatically be posted to Facebook when they are submitted.  There appeared to be no way to do this via the module and I had played around with Rules to see if that would work, but no luck./p/div/div/div

Colan Schwartz: Integrating remote data into Drupal 7 and exposing it to Views

Mon, 02/16/2015 - 20:45
section class=field field-name-field-blog-topics field-type-taxonomy-term-reference field-label-inline clearfix view-mode-rss clearfix h2 class=field-labelTopics:nbsp;/h2 ul class=field-items li class=field-item even rel=schema:keywords a href=/blog-topics/drupal-planet typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Drupal Planet/a /li li class=field-item odd rel=schema:keywords a href=/blog-topics/drupal-7 typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Drupal 7/a /li li class=field-item even rel=schema:keywords a href=/blog-topics/web-services typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Web Services/a /li li class=field-item odd rel=schema:keywords a href=/blog-topics/structured-content typeof=skos:Concept property=rdfs:label skos:prefLabel datatype=Structured Content/a /li /ul /section div class=field field-name-field-body field-type-text-long field-label-hidden view-mode-rssdiv class=field-itemsdiv class=field-item even property=schema:articleBodypDrupal's strength as a content management framework is in its ability to effectively manage and display structured content through its Web user interface. However, the out-of-the-box system assumes all data is local (stored in the database). This can present challenges when attempting to integrate remote data stored in other systems. You cannot, by default, display non-local records as pages. While setting this up is in itself a challenge, it is an even bigger challenge to manipulate, aggregate and display this data through a href=https://www.drupal.org/project/viewsViews/a./p pI've split this article into the following sections and subsections. Click on any of these to jump directly to the one of them./p ollia href=#introIntroduction/a/li lia href=#recent-changesWhat's Changed/a/li li a href=#architectureArchitecture/a ol type=ali a href=#remote-entity-definitionRemote entity definition/a ullia href=#remote-entity-definition-notesNotes/a/li /ul/li lia href=#access-to-remote-propertiesAccess to remote properties/a/li li a href=#remote-property-definitionRemote property definition/a ullia href=#remote-property-definition-notesNotes/a/li /ul/li lia href=#entity-instances-as-web-pagesEntity instances as Web pages/a/li lia href=#web-services-integrationWeb services integration/a/li lia href=#temporary-local-storageTemporary local storage/a/li li a href=#implementing-the-remote-connection-classImplementing the remote connection class/a ullia href=#clients-remote-entity-interface-implementationsClientsRemoteEntityInterface implementations/a/li lia href=#clients-connection-base-overridesParent overrides/a/li lia href=#local-methodsLocal methods/a/li /ul/li li a href=#implementing-the-remote-query-classImplementing the remote query class/a ullia href=#class-variables-and-constructorClass variables and constructor/a/li lia href=#setting-conditionsSetting conditions/a/li lia href=#executing-the-remote-queryExecuting the remote query/a/li lia href=#unmarshalling-the-response-data-and-returning-itUnmarshalling the response data and returning it/a/li lia href=#error-handlingError handling/a/li /ul/li /ol/li li a href=#views-supportViews support/a ol type=alia href=#views-basic-set-upBasic set-up/a/li lia href=#converting-from-an-efqConverting from an EntityFieldQuery/a/li /ol/li lia href=#alternativesAlternatives/a/li lia href=#referencesReferences/a/li /olhr /h2a name=intro id=introIntroduction/a/h2 pThis exposition is effectively a follow-up to some excellent articles from years past:/p ulliLarry Garfield's a href=http://www.palantir.net/blog/remote-data-drupal-museums-and-web-2009Remote Data in Drupal: Museums and the Web (2009)/a/li liFlorian Loretan's a href=http://www.wunderkraut.com/blog/remote-entities-in-drupal-7/2012-10-25Remote entities in Drupal 7 (2012)/a/li /ulpI'd recommend reading them for background information./p pThe first article (written in the Drupal 6 days) describes a Wipe/rebuild import method (Method 3) to bring remote data into Drupal. That's basically what we'll be discussing here, but there is now a standard method for doing so. What's interesting is that future plans mentioned there included a href=https://www.drupal.org/node/443422per-field storage engines/a (with some being remote). The idea never made it very far. This is most likely because grabbing field data from multiple locations is far too inefficient (multiple Web-service calls) compared to fetching an entire record from a single location./p pTaking a look at the second article, you can now see that Drupal 7 is dominant, and we have more tools at our disposal, but at the time this one was written, we still didn't have all of them in place. We did, however, have the following APIs for dealing with a href=https://www.drupal.org/node/1261744entities/a./p ollia href=https://api.drupal.org/api/drupal/includes%21entity.inc/7The entity API in Drupal Core/a/li lia href=https://www.drupal.org/project/entityThe Entity API contributed module/a/li /olh2a name=recent-changes id=recent-changesWhat's Changed/a/h2 pWe now have another API, the a href=https://www.drupal.org/project/remote_entityRemote Entity API/a, which was inspired by Florian's article. As you can imagine, this API is dependent on the Entity API which is in turn dependent on the Drupal Core's entity functionality./p pI recently added a href=https://www.drupal.org/node/2283083support for this new API/a to a href=https://www.drupal.org/project/efq_viewsEntityFieldQuery Views Backend/a, the module allowing Views to work with data stored outside of the local SQL database. Previously, it supported non-SQL data, but still assumed that this data was local. Tying these two components together gives us what we need to achieve our goal./p h2a name=architecture id=architectureArchitecture/a/h2 pSo we really need to take advantage of the three (3) entity APIs to load and display individual remote records./p ollia href=https://api.drupal.org/api/drupal/includes%21entity.inc/7The entity API in Drupal Core/a/li lia href=https://www.drupal.org/project/entityThe Entity API contributed module/a/li lia href=https://www.drupal.org/project/remote_entityThe Remote Entity API contributed module/a/li /olpThe first provides basic entity functionality in Drupal. The second adds enhanced functionality for custom entities. The third and final API adds additional handling mechanisms for working with any remote data. /ppWe'll need the following contributed modules to make all of this work./p ullia href=https://www.drupal.org/project/ctoolsChaos tool suite (ctools)/a/li lia href=https://www.drupal.org/project/entityEntity API/a/li lia href=https://www.drupal.org/project/clientsWeb Service Clients/a/li lia href=https://www.drupal.org/project/remote_entityRemote Entity API/a/li lia href=https://www.drupal.org/project/viewsViews/a/li lia href=https://www.drupal.org/project/efq_viewsEntityFieldQuery Views Backend/a/li /ulpIn addition to the above, a new custom module is necessary. I recommend something like emsiteshortname_entities_remote/em for the machine name. You can have another one, emsiteshortname_entities_local/em, for local entities without all of the remote code if necessary. In the em.info/em file, add emremote_entity/em (the Remote Entity API) as a dependency./p pYou'll want to divide your module file into at least three (3) parts:/p ollistrongEntity APIs:/strong Code for defining remote entities through any of the above entity APIs. (Part I)/li listrongDrupal Core:/strong Code for implementing Drupal Core hooks. This is basically a a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_menu/7hook_menu()/a implementation with some helper functions to get your entity instances to show up at specific paths based on the remote entity IDs. (Part II)/li listrongWeb Service Clients:/strong Code for implementing what's necessary for the a href=https://www.drupal.org/project/clientsWeb Service Clients/a module, a prerequisite for the Remote Entity API. It's essentially the external communications component for accessing your remote data. (Part III)/li /olpMost of the code will be in PHP class files you'll want in a emclasses/em subdirectory (autoloaded by defining these in your em.info/em file), but you'll still need some code in your main module file./p pWe'll be adding only one new entity in this exercise, but the code is extensible enough to allow for more. Once one of these is set up, adding more is (in most cases) trivial./p h3a name=remote-entity-definition id=remote-entity-definitionRemote entity definition/a/h3 pYour basic remote entity definitions will exist in the Entity APIs section of your module file, Part I. Within the a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_entity_info/7hook_entity_info()/a implementation, you'll see that different properties within the definition will be used by different layers, the three APIs./p pFor the following examples, let's assume we have a remote emevent/em data type./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/****************************************************************************br / ** Entity APIsbr / ****************************************************************************/br /br //**br / * Implements hook_entity_info().br / *br / * @todo Add 'bundles' for different types of remote content.br / * @todo Add 'entity keys' =gt; 'needs remote save' if remote saving required.br / * @todo Remove 'static cache' and 'field cache' settings after development.br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_entity_info/spanspan style=color: #007700() {br /  /spanspan style=color: #0000BB$entities/spanspan style=color: #007700[/spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700] = array(br /br /    /spanspan style=color: #FF8000// Core properties.br /    /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000'Event'/spanspan style=color: #007700),br /    /spanspan style=color: #DD0000'controller class' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'RemoteEntityAPIDefaultController'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'base table' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_events'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'uri callback' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'entity_class_uri'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'label callback' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'remote_entity_entity_label'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'fieldable' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'entity keys' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'id' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'eid'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'event_name'/spanspan style=color: #007700,br /    ),br /    /spanspan style=color: #DD0000'view modes' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'full' /spanspan style=color: #007700=gt; array(br /        /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000'Full content'/spanspan style=color: #007700),br /        /spanspan style=color: #DD0000'custom settings' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700,br /      ),br /    ),br /    /spanspan style=color: #DD0000'static cache' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'field cache' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700,br /br /    /spanspan style=color: #FF8000// Entity API properties.br /    /spanspan style=color: #DD0000'entity class' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'SiteshortnameEvent'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'module' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'metadata controller class' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'RemoteEntityAPIDefaultMetadataController'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'views controller class' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'EntityDefaultViewsController'/spanspan style=color: #007700,br /br /    /spanspan style=color: #FF8000// Remote Entity API properties.br /    /spanspan style=color: #DD0000'remote base table' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_events'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'remote entity keys' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'remote id' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'event_id'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'event_name'/spanspan style=color: #007700,br /    ),br /    /spanspan style=color: #DD0000'expiry' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #FF8000// Number of seconds before a locally cached instance must be refreshedbr /      // from the remote source.br /      /spanspan style=color: #DD0000'expiry time' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB600/spanspan style=color: #007700,br /      /spanspan style=color: #FF8000// A boolean indicating whether or not to delete expired local entitiesbr /      // on cron.br /      /spanspan style=color: #DD0000'purge' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700,br /    ),br /  );br /br /  /spanspan style=color: #FF8000// Get the property map data.br /  /spanspan style=color: #0000BB$remote_properties /spanspan style=color: #007700= /spanspan style=color: #0000BBsiteshortname_entities_remote_get_remote_properties/spanspan style=color: #007700();br /br /  /spanspan style=color: #FF8000// Assign each map to its corresponding entity.br /  /spanspan style=color: #007700foreach (/spanspan style=color: #0000BB$entities /spanspan style=color: #007700as /spanspan style=color: #0000BB$key /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$einfo/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BB$entities/spanspan style=color: #007700[/spanspan style=color: #0000BB$key/spanspan style=color: #007700][/spanspan style=color: #DD0000'property map'/spanspan style=color: #007700] =br /      /spanspan style=color: #0000BBdrupal_map_assoc/spanspan style=color: #007700(/spanspan style=color: #0000BBarray_keys/spanspan style=color: #007700(/spanspan style=color: #0000BB$remote_properties/spanspan style=color: #007700[/spanspan style=color: #0000BB$key/spanspan style=color: #007700]));br /  }br /br /  /spanspan style=color: #FF8000// Return all of the entity information.br /  /spanspan style=color: #007700return /spanspan style=color: #0000BB$entities/spanspan style=color: #007700;br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=remote-entity-definition-notes id=remote-entity-definition-notesNotes/a/h4 olliJust like the entity type emnode/em, which is subdivided into emcontent types/em (generically referred to as embundles/em in Drupal-speak), we can subdivide remote entities into their own bundles. In this case, we could have a High-school event bundle and a College event bundle that vary slightly, but instances of both would still be members of the entity type Event. We won't be setting this up here though./li liIn this article, we won't be covering remote saving (only remote loading), but it is possible through the remote API./li liMake sure to adjust the cache settings properly once development is complete./li liDetailed documentation on the APIs is available for a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_entity_info/7the Core entity API/a, a href=http://cgit.drupalcode.org/entity/tree/entity.api.phpthe Entity API/a, and the a href=http://cgit.drupalcode.org/remote_entity/tree/remote_entity.api.phpthe Remote Entity API/a./li /olh3a name=access-to-remote-properties id=access-to-remote-propertiesAccess to remote properties/a/h3 pAs we're not using the a href=https://api.drupal.org/api/drupal/modules!field!field.module/group/field/7Field API/a to attach information to our entities, we need to do it with properties. The code below exposes the data we'll define shortly./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * Implements hook_entity_property_info_alter().br / *br / * This is needed to use wrappers to access the remote entitybr / * data in the entity_data property of remote entities.br / *br / * @see: Page 107 of the Programming Drupal 7 Entities book.  The code below isbr / *   a variation on it.br / * @todo: Remove whenever this gets added to the remote_entity module.br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_entity_property_info_alter/spanspan style=color: #007700(amp;/spanspan style=color: #0000BB$info/spanspan style=color: #007700) {br /br /  /spanspan style=color: #FF8000// Set the entity types and get their properties.br /  /spanspan style=color: #0000BB$entity_types /spanspan style=color: #007700= array(br /    /spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700,br /  );br /br /  /spanspan style=color: #0000BB$remote_properties /spanspan style=color: #007700= /spanspan style=color: #0000BBsiteshortname_entities_remote_get_remote_properties/spanspan style=color: #007700();br /br /  /spanspan style=color: #FF8000// Assign the property data to each entity.br /  /spanspan style=color: #007700foreach (/spanspan style=color: #0000BB$entity_types /spanspan style=color: #007700as /spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BB$properties /spanspan style=color: #007700= amp;/spanspan style=color: #0000BB$info/spanspan style=color: #007700[/spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700][/spanspan style=color: #DD0000'properties'/spanspan style=color: #007700];br /    /spanspan style=color: #0000BB$entity_data /spanspan style=color: #007700= amp;/spanspan style=color: #0000BB$properties/spanspan style=color: #007700[/spanspan style=color: #DD0000'entity_data'/spanspan style=color: #007700];br /    /spanspan style=color: #0000BB$pp /spanspan style=color: #007700= amp;/spanspan style=color: #0000BB$remote_properties/spanspan style=color: #007700[/spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700];br /    /spanspan style=color: #0000BB$entity_data/spanspan style=color: #007700[/spanspan style=color: #DD0000'type'/spanspan style=color: #007700] = /spanspan style=color: #DD0000'remote_entity_' /spanspan style=color: #007700. /spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700;br /br /    /spanspan style=color: #FF8000// Set the default getter callback for each property.br /    /spanspan style=color: #007700foreach (/spanspan style=color: #0000BB$pp /spanspan style=color: #007700as /spanspan style=color: #0000BB$key /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$pinfo/spanspan style=color: #007700) {br /      /spanspan style=color: #0000BB$pp/spanspan style=color: #007700[/spanspan style=color: #0000BB$key/spanspan style=color: #007700][/spanspan style=color: #DD0000'getter callback'/spanspan style=color: #007700] = /spanspan style=color: #DD0000'entity_property_verbatim_get'/spanspan style=color: #007700;br /    }br /br /    /spanspan style=color: #FF8000// Assign the updated property info to the entity info.br /    /spanspan style=color: #0000BB$entity_data/spanspan style=color: #007700[/spanspan style=color: #DD0000'property info'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$pp/spanspan style=color: #007700;br /  }br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h3a name=remote-property-definition id=remote-property-definitionRemote property definition/a/h3 pThis is where we define the field (or in this case property) information, the data attached to each entity, that we exposed above./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * Get remote property information for remote entities.br / *br / * @returnbr / *   An array of property information keyed by entity type.br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_get_remote_properties/spanspan style=color: #007700() {br /br /  /spanspan style=color: #FF8000// Initialize a list of entity properties.br /  /spanspan style=color: #0000BB$properties /spanspan style=color: #007700= array();br /br /  /spanspan style=color: #FF8000// Define properties for the entity type.br /  /spanspan style=color: #0000BB$properties/spanspan style=color: #007700[/spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700] = array(br /br /    /spanspan style=color: #FF8000// Event information.br /    /spanspan style=color: #DD0000'event_id' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Remote Event ID'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'type' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'integer'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'description' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'The remote attribute id.'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'views' /spanspan style=color: #007700=gt; array(br /        /spanspan style=color: #DD0000'filter' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_views_handler_filter_event_id'/spanspan style=color: #007700,br /      ),br /    ),br /    /spanspan style=color: #DD0000'event_date' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Date'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'type' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'date'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'description' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'The remote attribute date.'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'views' /spanspan style=color: #007700=gt; array(br /        /spanspan style=color: #DD0000'filter' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_views_handler_filter_event_date'/spanspan style=color: #007700,br /      ),br /    ),br /    /spanspan style=color: #DD0000'event_details' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Details'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'type' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'text'/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'description' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'The remote attribute details.'/spanspan style=color: #007700,br /    ),br /  );br /br /  /spanspan style=color: #FF8000// Return all of the defined property info.br /  /spanspan style=color: #007700return /spanspan style=color: #0000BB$properties/spanspan style=color: #007700;br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=remote-property-definition-notes id=remote-property-definition-notesNotes/a/h4 olliTry to remember the distinction between local and remote entity IDs. At the moment, we're only interested in emremote/em properties so we don't don't need to worry about local IDs just yet./li liDon't worry too much about the Views filters. These are Views filter handler classes. They're only necessary if you'd like custom filters for the respective properties./li /olh3a name=entity-instances-as-web-pages id=entity-instances-as-web-pagesEntity instances as Web pages/a/h3 pThis starts the Core Hooks section of the module file, Part II. In this section, we're providing each remote data instance as a Web page just like standard local content within Drupal via emnodes/em./pp /ppThe a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_menu/7hook_menu()/a implementation responds to hits to the emevent/EVENT_ID/em path, loads the object, themes all of the data, and then returns it for display as a page. We're assuming all of your HTML output will be in a template in the emincludes/siteshortname_entities_remote.theme.inc/em file in your module's directory./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/****************************************************************************br / ** Drupal Corebr / ****************************************************************************/br /br //**br / * Implements hook_menu().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_menu/spanspan style=color: #007700() {br /  /spanspan style=color: #0000BB$items /spanspan style=color: #007700= array();br /br /  /spanspan style=color: #0000BB$items/spanspan style=color: #007700[/spanspan style=color: #DD0000'event/%siteshortname_entities_remote_event'/spanspan style=color: #007700] = array(br /    /spanspan style=color: #DD0000'title' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Remote Event'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'page callback' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_event_view'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'page arguments' /spanspan style=color: #007700=gt; array(/spanspan style=color: #0000BB1/spanspan style=color: #007700),br /    /spanspan style=color: #DD0000'access arguments' /spanspan style=color: #007700=gt; array(/spanspan style=color: #DD0000'access content'/spanspan style=color: #007700),br /  );br /br /  return /spanspan style=color: #0000BB$items/spanspan style=color: #007700;br /}br /br //spanspan style=color: #FF8000/**br / * Menu autoloader wildcard for path 'event/REMOTE_ID'.br / *br / * @see hook_menu() documentation.br / * @param $remote_idbr / *   The remote ID of the record to load.br / * @returnbr / *   The loaded object, or FALSE on failure.br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_event_load/spanspan style=color: #007700(/spanspan style=color: #0000BB$remote_id/spanspan style=color: #007700) {br /  return /spanspan style=color: #0000BBremote_entity_load_by_remote_id/spanspan style=color: #007700(/spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700, /spanspan style=color: #0000BB$remote_id/spanspan style=color: #007700);br /}br /br //spanspan style=color: #FF8000/**br / * Page callback for path 'event/%remote_id'.br / *br / * @param $eventbr / *   The auto-loaded object.br / * @returnbr / *   The themed output for the event page.br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_event_view/spanspan style=color: #007700(/spanspan style=color: #0000BB$event/spanspan style=color: #007700) {br /  /spanspan style=color: #0000BB$fullname /spanspan style=color: #007700= /spanspan style=color: #0000BB$event/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBname/spanspan style=color: #007700;br /  /spanspan style=color: #0000BBdrupal_set_title/spanspan style=color: #007700(/spanspan style=color: #0000BB$fullname/spanspan style=color: #007700);br /  /spanspan style=color: #0000BB$event_output /spanspan style=color: #007700= /spanspan style=color: #0000BBtheme/spanspan style=color: #007700(/spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700, array(br /    /spanspan style=color: #DD0000'event' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$event/spanspan style=color: #007700,br /  ));br /  return /spanspan style=color: #0000BB$event_output/spanspan style=color: #007700;br /}br /br //spanspan style=color: #FF8000/**br / * Implements hook_theme().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_theme/spanspan style=color: #007700() {br /  return array(br /    /spanspan style=color: #DD0000'siteshortname_entities_remote_event' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'variables' /spanspan style=color: #007700=gt; array(/spanspan style=color: #DD0000'event' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBNULL/spanspan style=color: #007700),br /      /spanspan style=color: #DD0000'file' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'includes/siteshortname_entities_remote.theme.inc'/spanspan style=color: #007700,br /    ),br /  );br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pThere's one more thing to do here. In our emhook_entity_info()/em implementation, we stated the following:/p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /    /spanspan style=color: #DD0000'entity class' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'SiteshortnameEvent'/spanspan style=color: #007700,br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pWe could have used emEntity/em here instead of emSiteshortnameEvent/em, but we want a custom class here so that we can override the URL path for these entities. So add the following class:/p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #007700class /spanspan style=color: #0000BBSiteshortnameEvent /spanspan style=color: #007700extends /spanspan style=color: #0000BBEntity /spanspan style=color: #007700{br /  /spanspan style=color: #FF8000/**br /   * Override defaultUri().br /   */br /  /spanspan style=color: #007700protected function /spanspan style=color: #0000BBdefaultUri/spanspan style=color: #007700() {br /    return array(/spanspan style=color: #DD0000'path' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'event/' /spanspan style=color: #007700. /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_id/spanspan style=color: #007700);br /  }br /} br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h3a name=web-services-integration id=web-services-integrationWeb services integration/a/h3 We're now onto Part III, setting up Web-service endpoints and associating remote resources with entities. This is done through the implementation of a few a href=https://www.drupal.org/project/clientsWeb Service Clients/a hooks. div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/****************************************************************************br / ** Web Service Clientsbr / ****************************************************************************/br /br //**br / * Implements hook_clients_connection_type_info().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_clients_connection_type_info/spanspan style=color: #007700() {br /  return array(br /    /spanspan style=color: #DD0000'our_rest' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'label'  /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000'REST Data Services'/spanspan style=color: #007700),br /      /spanspan style=color: #DD0000'description' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000'Connects to our data service using REST endpoints.'/spanspan style=color: #007700),br /      /spanspan style=color: #DD0000'tests' /spanspan style=color: #007700=gt; array(br /        /spanspan style=color: #DD0000'event_retrieve_raw' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'SiteshortnameEntitiesRemoteConnectionTestEventRetrieveRaw'/spanspan style=color: #007700,br /      ),br /      /spanspan style=color: #DD0000'interfaces' /spanspan style=color: #007700=gt; array(br /        /spanspan style=color: #DD0000'ClientsRemoteEntityInterface'/spanspan style=color: #007700,br /      ),br /    ),br /  );br /}br /br //spanspan style=color: #FF8000/**br / * Implements hook_clients_default_connections().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_clients_default_connections/spanspan style=color: #007700() {br /br /  /spanspan style=color: #0000BB$connections/spanspan style=color: #007700[/spanspan style=color: #DD0000'my_rest_connection'/spanspan style=color: #007700] = new /spanspan style=color: #0000BBclients_connection_our_rest/spanspan style=color: #007700(array(br /    /spanspan style=color: #DD0000'endpoint' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'https://data.example.com'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'configuration' /spanspan style=color: #007700=gt; array(br /      /spanspan style=color: #DD0000'username' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000''/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'password' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000''/spanspan style=color: #007700,br /    ),br /    /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Our REST Service'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'type' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'our_rest'/spanspan style=color: #007700,br /  ), /spanspan style=color: #DD0000'clients_connection'/spanspan style=color: #007700);br /br /  return /spanspan style=color: #0000BB$connections/spanspan style=color: #007700;br /}br /br //spanspan style=color: #FF8000/**br / * Implements hook_clients_default_resources().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_clients_default_resources/spanspan style=color: #007700() {br /  /spanspan style=color: #0000BB$resources/spanspan style=color: #007700[/spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700] = new /spanspan style=color: #0000BBclients_resource_remote_entity/spanspan style=color: #007700(array(br /    /spanspan style=color: #DD0000'component' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'siteshortname_entities_remote_event'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'connection' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'my_rest_connection'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'label' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'Resource for remote events'/spanspan style=color: #007700,br /    /spanspan style=color: #DD0000'type' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'remote_entity'/spanspan style=color: #007700,br /  ), /spanspan style=color: #DD0000'clients_resource'/spanspan style=color: #007700);br /br /  return /spanspan style=color: #0000BB$resources/spanspan style=color: #007700;br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pIn the first function, we're adding metadata for the connection. In the second one, we're setting the endpoint and its credentials. The third function is what ties our remote entity, defined earlier, with the remote resource. There's some information on this a href=https://www.drupal.org/node/1228830documentation page/a, but there's more in the a href=http://cgit.drupalcode.org/clients/tree/README.txtREADME file/a./p h3a name=temporary-local-storage id=temporary-local-storageTemporary local storage/a/h3 pWe'll need to store the remote data in a local table as a non-authoritative cache. The frequency with which it gets refreshed is up to you, as described earlier in this article. We'll need one table per entity. The good news is that we don't need to worry about the details; this is handled by the Remote Entity API. It provides a function returning the default schema. If you want to do anything different here, you are welcome to define your own./p pThe argument provided in the call is used for the table description as The base table for em[whatever you provide]/em. This will go in your emsiteshortname_entities_remote.install/em file./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * Implementation of hook_schema().br / */br //spanspan style=color: #007700function /spanspan style=color: #0000BBsiteshortname_entities_remote_schema/spanspan style=color: #007700() {br /  /spanspan style=color: #0000BB$schema /spanspan style=color: #007700= array(br /    /spanspan style=color: #DD0000'siteshortname_entities_remote_events' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBremote_entity_schema_table/spanspan style=color: #007700(/spanspan style=color: #DD0000'our remote event entity type'/spanspan style=color: #007700),br /  );br /br /  return /spanspan style=color: #0000BB$schema/spanspan style=color: #007700;br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pIf you don't actually want to save one or more of your remote entities locally (say because you have private data you'd rather not have stored on your publicly-accessible Web servers), you can alter this default behaviour by defining your own controller which overrides the emsave()/em method./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * Entity controller extending RemoteEntityAPIDefaultControllerbr / *br / * For most of our cases the default controller is fine, but we can usebr / * this one for entities we don't want stored locally.  Override the savebr / * behaviour and do not keep a local cached copy.br / */br //spanspan style=color: #007700class /spanspan style=color: #0000BBSiteshortnameEntitiesRemoteNoLocalAPIController /spanspan style=color: #007700extends /spanspan style=color: #0000BBRemoteEntityAPIDefaultController /spanspan style=color: #007700{br /br /  /spanspan style=color: #FF8000/**br /   * Don't actually save anything.br /   */br /  /spanspan style=color: #007700public function /spanspan style=color: #0000BBsave/spanspan style=color: #007700(/spanspan style=color: #0000BB$entity/spanspan style=color: #007700, /spanspan style=color: #0000BBDatabaseTransaction $transaction /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BB$entity/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBeid /spanspan style=color: #007700= /spanspan style=color: #0000BBuniqid/spanspan style=color: #007700();br /  }br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h3a name=implementing-the-remote-connection-class id=implementing-the-remote-connection-classImplementing the remote connection class/a/h3 pCreate a file for the connection class./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * @filebr / * Contains the clients_connection_our_rest class.br / */br /br //**br / * Set up a client connection to our REST services.br / *br / *  @todo Make private functions private once development is done.br / */br //spanspan style=color: #007700class /spanspan style=color: #0000BBclients_connection_our_rest /spanspan style=color: #007700extends /spanspan style=color: #0000BBclients_connection_basebr /  /spanspan style=color: #007700implements /spanspan style=color: #0000BBClientsConnectionAdminUIInterface/spanspan style=color: #007700, /spanspan style=color: #0000BBClientsRemoteEntityInterface /spanspan style=color: #007700{br /br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pWe'll now divide the contents of said file into three (3) sections, emClientsRemoteEntityInterface/em implementations, emclients_connection_base/em overrides and local methods./p h4a name=clients-remote-entity-interface-implementations id=clients-remote-entity-interface-implementationsClientsRemoteEntityInterface implementations/a/h4 pAs you can see below, we've got three (3) methods here./p ulliemremote_entity_load()/em will load a remote entity with the provided remote ID./li liementity_property_type_map()/em is supposedly required to map remote properties to local ones, but it wasn't clear to me how this gets used./li liemgetRemoteEntityQuery()/em returns a query object, either a select, insert or update based on whichever one was requested./li /uldiv class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**************************************************************************br /   * ClientsRemoteEntityInterface implementations.br /   **************************************************************************/br /br /  /**br /   * Load a remote entity.br /   *br /   * @param $entity_typebr /   *   The entity type to load.br /   * @param $idbr /   *   The (remote) ID of the entity.br /   *br /   * @returnbr /   *  An entity object.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBremote_entity_load/spanspan style=color: #007700(/spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700, /spanspan style=color: #0000BB$id/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BB$query /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetRemoteEntityQuery/spanspan style=color: #007700(/spanspan style=color: #DD0000'select'/spanspan style=color: #007700);br /    /spanspan style=color: #0000BB$query/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBbase/spanspan style=color: #007700(/spanspan style=color: #0000BB$entity_type/spanspan style=color: #007700);br /    /spanspan style=color: #0000BB$query/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBentityCondition/spanspan style=color: #007700(/spanspan style=color: #DD0000'entity_id'/spanspan style=color: #007700, /spanspan style=color: #0000BB$id/spanspan style=color: #007700);br /    /spanspan style=color: #0000BB$result /spanspan style=color: #007700= /spanspan style=color: #0000BB$query/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBexecute/spanspan style=color: #007700();br /br /    /spanspan style=color: #FF8000// There's only one. Same pattern as entity_load_single().br /    /spanspan style=color: #007700return /spanspan style=color: #0000BBreset/spanspan style=color: #007700(/spanspan style=color: #0000BB$result/spanspan style=color: #007700);br /  }br /br /  /spanspan style=color: #FF8000/**br /   * Provide a map of remote property types to Drupal types.br /   *br /   * Roughly analogous to _entity_metadata_convert_schema_type().br /   *br /   * @returnbr /   *   An array whose keys are remote property types as used as types for fieldsbr /   *   in hook_remote_entity_query_table_info(), and whose values are typesbr /   *   recognized by the Entity Metadata API (as listed in the documentation forbr /   *   hook_entity_property_info()).br /   *   If a remote property type is not listed here, it will be mapped to 'text'br /   *   by default.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBentity_property_type_map/spanspan style=color: #007700() {br /    return array(br /      /spanspan style=color: #DD0000'EntityCollection' /spanspan style=color: #007700=gt; /spanspan style=color: #DD0000'listlt;stringgt;'/spanspan style=color: #007700,br /    );br /  }br /br /  /spanspan style=color: #FF8000/**br /   * Get a new RemoteEntityQuery object appropriate for the connection.br /   *br /   * @param $query_typebr /   *  (optional) The type of the query. Defaults to 'select'.br /   *br /   * @returnbr /   *  A remote query object of the type appropriate to the query type.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBgetRemoteEntityQuery/spanspan style=color: #007700(/spanspan style=color: #0000BB$query_type /spanspan style=color: #007700= /spanspan style=color: #DD0000'select'/spanspan style=color: #007700) {br /    switch (/spanspan style=color: #0000BB$query_type/spanspan style=color: #007700) {br /      case /spanspan style=color: #DD0000'select'/spanspan style=color: #007700:br /        return new /spanspan style=color: #0000BBOurRestRemoteSelectQuery/spanspan style=color: #007700(/spanspan style=color: #0000BB$this/spanspan style=color: #007700);br /      case /spanspan style=color: #DD0000'insert'/spanspan style=color: #007700:br /        return new /spanspan style=color: #0000BBOurRestRemoteInsertQuery/spanspan style=color: #007700(/spanspan style=color: #0000BB$this/spanspan style=color: #007700);br /      case /spanspan style=color: #DD0000'update'/spanspan style=color: #007700:br /        return new /spanspan style=color: #0000BBOurRestRemoteUpdateQuery/spanspan style=color: #007700(/spanspan style=color: #0000BB$this/spanspan style=color: #007700);br /    }br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=clients-connection-base-overrides id=clients-connection-base-overridesParent overrides/a/h4 pThe only method we need to worry about here is emcallMethodArray()/em. Basically, it sets up the remote call./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**************************************************************************br /   * clients_connection_base overridesbr /   **************************************************************************/br /br /  /**br /   * Call a remote method with an array of parameters.br /   *br /   * This is intended for internal use from callMethod() andbr /   * clients_connection_call().br /   * If you need to call a method on given connection object, use callMethodbr /   * which has a nicer form.br /   *br /   * Subclasses do not necessarily have to override this method if theirbr /   * connection type does not make sense with this.br /   *br /   * @param $methodbr /   *  The name of the remote method to call.br /   * @param $method_paramsbr /   *  An array of parameters to passed to the remote method.br /   *br /   * @returnbr /   *  Whatever is returned from the remote site.br /   *br /   * @throws Exception on error from the remote site.br /   *  It's up to subclasses to implement this, as the test for an error andbr /   *  the way to get information about it varies according to service type.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBcallMethodArray/spanspan style=color: #007700(/spanspan style=color: #0000BB$method/spanspan style=color: #007700, /spanspan style=color: #0000BB$method_params /spanspan style=color: #007700= array()) {br /br /    switch (/spanspan style=color: #0000BB$method/spanspan style=color: #007700) {br /      case /spanspan style=color: #DD0000'makeRequest'/spanspan style=color: #007700:br /br /        /spanspan style=color: #FF8000// Set the parameters.br /        /spanspan style=color: #0000BB$resource_path /spanspan style=color: #007700= /spanspan style=color: #0000BB$method_params/spanspan style=color: #007700[/spanspan style=color: #0000BB0/spanspan style=color: #007700];br /        /spanspan style=color: #0000BB$http_method /spanspan style=color: #007700= /spanspan style=color: #0000BB$method_params/spanspan style=color: #007700[/spanspan style=color: #0000BB1/spanspan style=color: #007700];br /        /spanspan style=color: #0000BB$data /spanspan style=color: #007700= isset(/spanspan style=color: #0000BB$method_params/spanspan style=color: #007700[/spanspan style=color: #0000BB2/spanspan style=color: #007700]) ? /spanspan style=color: #0000BB$method_params/spanspan style=color: #007700[/spanspan style=color: #0000BB2/spanspan style=color: #007700] : array();br /br /        /spanspan style=color: #FF8000// Make the request.br /        /spanspan style=color: #0000BB$results /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBmakeRequest/spanspan style=color: #007700(/spanspan style=color: #0000BB$resource_path/spanspan style=color: #007700, /spanspan style=color: #0000BB$http_method/spanspan style=color: #007700, /spanspan style=color: #0000BB$data/spanspan style=color: #007700);br /        break;br /    }br /br /    return /spanspan style=color: #0000BB$results/spanspan style=color: #007700;br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=local-methods id=local-methodsLocal methods/a/h4 We're assuming REST here, but you can use any protocol. pWe have a emmakeRequest()/em method, which actually performs the remote call, and emhandleRestError()/em which deals with any errors which are returned./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**************************************************************************br /   * Local methodsbr /   **************************************************************************/br /br /  /**br /   * Make a REST request.br /   *br /   * Originally from clients_connection_drupal_services_rest_7-gt;makeRequest().br /   * Examples:br /   * Retrieve an event:br /   *  makeRequest('event?eventId=ID', 'GET');br /   * Update a node:br /   *  makeRequest('node/NID', 'POST', $data);br /   *br /   * @param $resource_pathbr /   *  The path of the resource. Eg, 'node', 'node/1', etc.br /   * @param $http_methodbr /   *  The HTTP method. One of 'GET', 'POST', 'PUT', 'DELETE'. For an explanationbr /   *  of how the HTTP method affects the resource request, see the Servicesbr /   *  documentation at http://drupal.org/node/783254.br /   * @param $data = array()br /   *  (Optional) An array of data to pass to the request.br /   * @param boolean $data_as_headersbr /   *   Data will be sent in the headers if this is set to TRUE.br /   *br /   * @returnbr /   *  The data from the request response.br /   *br /   *  @todo Update the first two test classes to not assume a SimpleXMLElement.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBmakeRequest/spanspan style=color: #007700(/spanspan style=color: #0000BB$resource_path/spanspan style=color: #007700, /spanspan style=color: #0000BB$http_method/spanspan style=color: #007700, /spanspan style=color: #0000BB$data /spanspan style=color: #007700= array(), /spanspan style=color: #0000BB$data_as_headers /spanspan style=color: #007700= /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Tap into this function's cache if there is one.br /    /spanspan style=color: #0000BB$request_cache_map /spanspan style=color: #007700= amp;/spanspan style=color: #0000BBdrupal_static/spanspan style=color: #007700(/spanspan style=color: #0000BB__FUNCTION__/spanspan style=color: #007700);br /br /    /spanspan style=color: #FF8000// Set the options.br /    /spanspan style=color: #0000BB$options /spanspan style=color: #007700= array(br /      /spanspan style=color: #DD0000'headers' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetHeaders/spanspan style=color: #007700(),  /spanspan style=color: #FF8000// Define if you need it.br /      /spanspan style=color: #DD0000'method'  /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$http_method/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'data'    /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$data/spanspan style=color: #007700,br /    );br /br /    /spanspan style=color: #FF8000// If cached, we have already issued this request during this page request sobr /    // just use the cached value.br /    /spanspan style=color: #0000BB$request_path /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBendpoint /spanspan style=color: #007700. /spanspan style=color: #0000BB$context_path /spanspan style=color: #007700. /spanspan style=color: #DD0000'/' /spanspan style=color: #007700. /spanspan style=color: #0000BB$resource_path/spanspan style=color: #007700;br /br /    /spanspan style=color: #FF8000// Either get the data from the cache or send a request for it.br /    /spanspan style=color: #007700if (isset(/spanspan style=color: #0000BB$request_cache_map/spanspan style=color: #007700[/spanspan style=color: #0000BB$request_path/spanspan style=color: #007700])) {br /      /spanspan style=color: #FF8000// Use the cached copy.br /      /spanspan style=color: #0000BB$response /spanspan style=color: #007700= /spanspan style=color: #0000BB$request_cache_map/spanspan style=color: #007700[/spanspan style=color: #0000BB$request_path/spanspan style=color: #007700];br /    } else {br /      /spanspan style=color: #FF8000// Not cached yet so fire off the request.br /      /spanspan style=color: #0000BB$response /spanspan style=color: #007700= /spanspan style=color: #0000BBdrupal_http_request/spanspan style=color: #007700(/spanspan style=color: #0000BB$request_path/spanspan style=color: #007700, /spanspan style=color: #0000BB$options/spanspan style=color: #007700);br /br /      /spanspan style=color: #FF8000// And then cache to avoid duplicate calls within the page request.br /      /spanspan style=color: #0000BB$request_cache_map/spanspan style=color: #007700[/spanspan style=color: #0000BB$request_path/spanspan style=color: #007700] = /spanspan style=color: #0000BB$response/spanspan style=color: #007700;br /    }br /br /    /spanspan style=color: #FF8000// Handle any errors and then return the response.br /    /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBhandleRestError/spanspan style=color: #007700(/spanspan style=color: #0000BB$request_path/spanspan style=color: #007700, /spanspan style=color: #0000BB$response/spanspan style=color: #007700);br /    return /spanspan style=color: #0000BB$response/spanspan style=color: #007700;br /  }br /br /  /spanspan style=color: #FF8000/**br /   * Common helper for reacting to an error from a REST call.br /   *br /   * Originally from clients_connection_drupal_services_rest_7-gt;handleRestError().br /   * Gets the error from the response, logs the error message,br /   * and throws an exception, which should be caught by the module making usebr /   * of the Clients connection API.br /   *br /   * @param $responsebr /   *  The REST response data, decoded.br /   *br /   * @throws Exceptionbr /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBhandleRestError/spanspan style=color: #007700(/spanspan style=color: #0000BB$request/spanspan style=color: #007700, /spanspan style=color: #0000BB$response/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Report and throw an error if we get anything unexpected.br /    /spanspan style=color: #007700if (!/spanspan style=color: #0000BBin_array/spanspan style=color: #007700(/spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBcode/spanspan style=color: #007700, array(/spanspan style=color: #0000BB200/spanspan style=color: #007700, /spanspan style=color: #0000BB201/spanspan style=color: #007700, /spanspan style=color: #0000BB202/spanspan style=color: #007700, /spanspan style=color: #0000BB204/spanspan style=color: #007700, /spanspan style=color: #0000BB404/spanspan style=color: #007700))) {br /br /      /spanspan style=color: #FF8000// Report error to the logs.br /      /spanspan style=color: #0000BBwatchdog/spanspan style=color: #007700(/spanspan style=color: #DD0000'clients'/spanspan style=color: #007700, /spanspan style=color: #DD0000'Error with REST request (@req). Error was code @code with error @error and message @message.'/spanspan style=color: #007700, array(br /        /spanspan style=color: #DD0000'@req'      /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$request/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'@code'     /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBcode/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'@error'    /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBerror/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'@message'  /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBstatus_message/spanspan style=color: #007700) ? /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBstatus_message /spanspan style=color: #007700: /spanspan style=color: #DD0000'(no message)'/spanspan style=color: #007700,br /      ), /spanspan style=color: #0000BBWATCHDOG_ERROR/spanspan style=color: #007700);br /br /      /spanspan style=color: #FF8000// Throw an error with which callers must deal.br /      /spanspan style=color: #007700throw new /spanspan style=color: #0000BBException/spanspan style=color: #007700(/spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000Clients connection error, got message '@message'./spanspan style=color: #007700, array(br /        /spanspan style=color: #DD0000'@message' /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBstatus_message/spanspan style=color: #007700) ? /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBstatus_message /spanspan style=color: #007700: /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBerror/spanspan style=color: #007700,br /      )), /spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBcode/spanspan style=color: #007700);br /    }br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h3a name=implementing-the-remote-query-class id=implementing-the-remote-query-classImplementing the remote query class/a/h3 pThis is where the magic happens. We need a new class file, emOurRestRemoteSelectQuery.class.php/em, that will assemble the select query and execute it based on any set conditions./p h4a name=class-variables-and-constructor id=class-variables-and-constructorClass variables and constructor/a/h4 pFirst, let's define the class, its variables and its constructor. It's a subclass of the emRemoteEntityQuery/em class. Most of the standard conditions would be added to the em$conditions/em array, but conditions handled in a special way (say those dealing with metadata) can be set up as variables themselves. In the example below, the constructor sets the active user as it can affect which data is returned. You can, however, set whatever you need to initialize your subclass, or leave it out entirely./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr //spanspan style=color: #FF8000/**br / * @filebr / * Contains the OurRestRemoteSelectQuery class.br / */br /br //**br / * Select query for our remote data.br / *br / * @todo Make vars protected once no longer developing.br / */br //spanspan style=color: #007700class /spanspan style=color: #0000BBOurRestRemoteSelectQuery /spanspan style=color: #007700extends /spanspan style=color: #0000BBRemoteEntityQuery /spanspan style=color: #007700{br /br /  /spanspan style=color: #FF8000/**br /   * Determines whether the query is RetrieveMultiple or Retrieve.br /   *br /   * The query is Multiple by default, until an ID condition causes it to bebr /   * single.br /   */br /  /spanspan style=color: #007700public /spanspan style=color: #0000BB$retrieve_multiple /spanspan style=color: #007700= /spanspan style=color: #0000BBTRUE/spanspan style=color: #007700;br /br /  /spanspan style=color: #FF8000/**br /   * An array of conditions on the query. These are grouped by the table theybr /   * are on.br /   */br /  /spanspan style=color: #007700public /spanspan style=color: #0000BB$conditions /spanspan style=color: #007700= array();br /br /  /spanspan style=color: #FF8000/**br /   * The from date filter for event searchesbr /   */br /  /spanspan style=color: #007700public /spanspan style=color: #0000BB$from_date /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700;br /br /  /spanspan style=color: #FF8000/**br /   * The to date filter for event searchesbr /   */br /  /spanspan style=color: #007700public /spanspan style=color: #0000BB$to_date /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700;br /br /  /spanspan style=color: #FF8000/**br /   * The user id.br /   */br /  /spanspan style=color: #007700public /spanspan style=color: #0000BB$user_id /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700;br /br /  /spanspan style=color: #FF8000/**br /   * Constructor to generically set up the user id condition ifbr /   * there is a current user.br /   *br /   * @param $connectionbr /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BB__construct/spanspan style=color: #007700(/spanspan style=color: #0000BB$connection/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BBparent/spanspan style=color: #007700::/spanspan style=color: #0000BB__construct/spanspan style=color: #007700(/spanspan style=color: #0000BB$connection/spanspan style=color: #007700);br /    if (/spanspan style=color: #0000BBuser_is_logged_in/spanspan style=color: #007700()) {br /      global /spanspan style=color: #0000BB$user/spanspan style=color: #007700;br /      /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBuseridCondition/spanspan style=color: #007700(/spanspan style=color: #0000BB$user/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBname/spanspan style=color: #007700);br /    }br /  }br /}br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=setting-conditions id=setting-conditionsSetting conditions/a/h4 pWe have three (3) methods which set conditions within the query. ementityCondition()/em sets conditions affecting entities in general. (The only entity condition supported here is the entity ID.) empropertyCondition()/em sets conditions related to properties specific to the type of data. For example, this could be a location filter for one or more events. Finally, we have emuseridCondition()/em which sets the query to act on behalf of a specific user. Here we simply record the current Drupal user./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**br /   * Add a condition to the query.br /   *br /   * Originally based on the entityCondition() method in EntityFieldQuery, butbr /   * largely from USDARemoteSelectQuery (Programming Drupal 7 Entities) andbr /   * MSDynamicsSoapSelectQuery.br /   *br /   * @param $namebr /   *  The name of the entity property.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBentityCondition/spanspan style=color: #007700(/spanspan style=color: #0000BB$name/spanspan style=color: #007700, /spanspan style=color: #0000BB$value/spanspan style=color: #007700, /spanspan style=color: #0000BB$operator /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// We only support the entity ID for now.br /    /spanspan style=color: #007700if (/spanspan style=color: #0000BB$name /spanspan style=color: #007700== /spanspan style=color: #DD0000'entity_id'/spanspan style=color: #007700) {br /br /      /spanspan style=color: #FF8000// Get the remote field name of the entity ID.br /      /spanspan style=color: #0000BB$field /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBentity_info/spanspan style=color: #007700[/spanspan style=color: #DD0000'remote entity keys'/spanspan style=color: #007700][/spanspan style=color: #DD0000'remote id'/spanspan style=color: #007700];br /br /      /spanspan style=color: #FF8000// Set the remote ID field to the passed value.br /      /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconditions/spanspan style=color: #007700[/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_base/spanspan style=color: #007700][] = array(br /        /spanspan style=color: #DD0000'field' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$field/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'value' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$value/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'operator' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$operator/spanspan style=color: #007700,br /      );br /br /      /spanspan style=color: #FF8000// Record that we'll only be retrieving a single item.br /      /spanspan style=color: #007700if (/spanspan style=color: #0000BBis_null/spanspan style=color: #007700(/spanspan style=color: #0000BB$operator/spanspan style=color: #007700) || (/spanspan style=color: #0000BB$operator /spanspan style=color: #007700== /spanspan style=color: #DD0000'='/spanspan style=color: #007700)) {br /        /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBretrieve_multiple /spanspan style=color: #007700= /spanspan style=color: #0000BBFALSE/spanspan style=color: #007700;br /      }br /    }br /    else {br /br /      /spanspan style=color: #FF8000// Report an invalid entity condition.br /      /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBthrowException/spanspan style=color: #007700(br /        /spanspan style=color: #DD0000'OURRESTREMOTESELECTQUERY_INVALID_ENTITY_CONDITION'/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'The query object can only accept the \'entity_id\' condition.'br /      /spanspan style=color: #007700);br /    }br /  }br /br /  /spanspan style=color: #FF8000/**br /   * Add a condition to the query, using local property keys.br /   *br /   * Based on MSDynamicsSoapSelectQuery::propertyCondition().br /   *br /   * @param $property_namebr /   *  A local property. Ie, a key in the $entity_info 'property map' array.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBpropertyCondition/spanspan style=color: #007700(/spanspan style=color: #0000BB$property_name/spanspan style=color: #007700, /spanspan style=color: #0000BB$value/spanspan style=color: #007700, /spanspan style=color: #0000BB$operator /spanspan style=color: #007700= /spanspan style=color: #0000BBNULL/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Make sure the entity base has been set up.br /    /spanspan style=color: #007700if (!isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBentity_info/spanspan style=color: #007700)) {br /      /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBthrowException/spanspan style=color: #007700(br /        /spanspan style=color: #DD0000'OURRESTREMOTESELECTQUERY_ENTITY_BASE_NOT_SET'/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'The query object was not set with an entity type.'br /      /spanspan style=color: #007700);br /    }br /br /    /spanspan style=color: #FF8000// Make sure that the provided property is valid.br /    /spanspan style=color: #007700if (!isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBentity_info/spanspan style=color: #007700[/spanspan style=color: #DD0000'property map'/spanspan style=color: #007700][/spanspan style=color: #0000BB$property_name/spanspan style=color: #007700])) {br /      /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBthrowException/spanspan style=color: #007700(br /        /spanspan style=color: #DD0000'OURRESTREMOTESELECTQUERY_INVALID_PROPERY'/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'The query object cannot set a non-existent property.'br /      /spanspan style=color: #007700);br /    }br /br /    /spanspan style=color: #FF8000// Adding a field condition (probably) automatically makes this a multiple.br /    // TODO: figure this out for sure!br /    /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBretrieve_multiple /spanspan style=color: #007700= /spanspan style=color: #0000BBTRUE/spanspan style=color: #007700;br /br /    /spanspan style=color: #FF8000// Use the property map to determine the remote field name.br /    /spanspan style=color: #0000BB$remote_field_name /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBentity_info/spanspan style=color: #007700[/spanspan style=color: #DD0000'property map'/spanspan style=color: #007700][/spanspan style=color: #0000BB$property_name/spanspan style=color: #007700];br /br /    /spanspan style=color: #FF8000// Set the condition for use during execution.br /    /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconditions/spanspan style=color: #007700[/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_base/spanspan style=color: #007700][] = array(br /      /spanspan style=color: #DD0000'field' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$remote_field_name/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'value' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$value/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'operator' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$operator/spanspan style=color: #007700,br /    );br /  }br /br /  /spanspan style=color: #FF8000/**br /   * Add a user id condition to the query.br /   *br /   * @param $user_idbr /   *   The user to search for appointments.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBuseridCondition/spanspan style=color: #007700(/spanspan style=color: #0000BB$user_id/spanspan style=color: #007700) {br /    /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBuser_id /spanspan style=color: #007700= /spanspan style=color: #0000BB$user_id/spanspan style=color: #007700;br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=executing-the-remote-query id=executing-the-remote-queryExecuting the remote query/a/h4 pThe emexecute()/em method marshals all of the conditions, passes the built request to the connection's emmakeRequest()/em that we saw earlier, calls emparseEventResponse()/em (which we'll investigate below) and then returns the list of remote entities that can now be used by Drupal./p pFeel free to ignore the authentication code if it's not required for your implementation. I left it in as an extended example of how this could be done./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**br /   * Run the query and return a result.br /   *br /   * @returnbr /   *  Remote entity objects as retrieved from the remote connection.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBexecute/spanspan style=color: #007700() {br /br /    /spanspan style=color: #FF8000// If there are any validation errors, don't perform a search.br /    /spanspan style=color: #007700if (/spanspan style=color: #0000BBform_set_error/spanspan style=color: #007700()) {br /      return array();br /    }br /br /    /spanspan style=color: #0000BB$querystring /spanspan style=color: #007700= array();br /br /    /spanspan style=color: #0000BB$path /spanspan style=color: #007700= /spanspan style=color: #0000BBvariable_get/spanspan style=color: #007700(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBbase_entity_type /spanspan style=color: #007700. /spanspan style=color: #DD0000'_resource_name'/spanspan style=color: #007700, /spanspan style=color: #DD0000''/spanspan style=color: #007700);br /br /    /spanspan style=color: #FF8000// Iterate through all of the conditions and add them to the query.br /    /spanspan style=color: #007700if (isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconditions/spanspan style=color: #007700[/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_base/spanspan style=color: #007700])) {br /      foreach (/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconditions/spanspan style=color: #007700[/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_base/spanspan style=color: #007700] as /spanspan style=color: #0000BB$condition/spanspan style=color: #007700) {br /        switch (/spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'field'/spanspan style=color: #007700]) {br /          case /spanspan style=color: #DD0000'event_id'/spanspan style=color: #007700:br /            /spanspan style=color: #0000BB$querystring/spanspan style=color: #007700[/spanspan style=color: #DD0000'eventId'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700];br /            break;br /          case /spanspan style=color: #DD0000'login_id'/spanspan style=color: #007700:br /            /spanspan style=color: #0000BB$querystring/spanspan style=color: #007700[/spanspan style=color: #DD0000'userId'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700];br /            break;br /        }br /      }br /    }br /br /    /spanspan style=color: #FF8000// From date parameter.br /    /spanspan style=color: #007700if (isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBfrom_date/spanspan style=color: #007700)) {br /      /spanspan style=color: #0000BB$querystring/spanspan style=color: #007700[/spanspan style=color: #DD0000'startDate'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBfrom_date/spanspan style=color: #007700;br /    }br /br /    /spanspan style=color: #FF8000// To date parameter.br /    /spanspan style=color: #007700if (isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBto_date/spanspan style=color: #007700)) {br /      /spanspan style=color: #0000BB$querystring/spanspan style=color: #007700[/spanspan style=color: #DD0000'endDate'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBto_date/spanspan style=color: #007700;br /    }br /br /    /spanspan style=color: #FF8000// Add user id based filter if present.br /    /spanspan style=color: #007700if (isset(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBuser_id/spanspan style=color: #007700)) {br /      /spanspan style=color: #0000BB$querystring/spanspan style=color: #007700[/spanspan style=color: #DD0000'userId'/spanspan style=color: #007700] = /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBuser_id/spanspan style=color: #007700;br /    }br /br /    /spanspan style=color: #FF8000// Assemble all of the query parameters.br /    /spanspan style=color: #007700if (/spanspan style=color: #0000BBcount/spanspan style=color: #007700(/spanspan style=color: #0000BB$querystring/spanspan style=color: #007700)) {br /      /spanspan style=color: #0000BB$path /spanspan style=color: #007700.= /spanspan style=color: #DD0000'?' /spanspan style=color: #007700. /spanspan style=color: #0000BBdrupal_http_build_query/spanspan style=color: #007700(/spanspan style=color: #0000BB$querystring/spanspan style=color: #007700);br /    }br /br /    /spanspan style=color: #FF8000// Make the request.br /    /spanspan style=color: #007700try {br /      /spanspan style=color: #0000BB$response /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconnection/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBmakeRequest/spanspan style=color: #007700(/spanspan style=color: #0000BB$path/spanspan style=color: #007700, /spanspan style=color: #DD0000'GET'/spanspan style=color: #007700);br /    } catch (/spanspan style=color: #0000BBException $e/spanspan style=color: #007700) {br /      if (/spanspan style=color: #0000BB$e/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetCode/spanspan style=color: #007700() == /spanspan style=color: #0000BBOUR_REST_LOGIN_REQUIRED_NO_SESSION/spanspan style=color: #007700) {br /        /spanspan style=color: #0000BBdrupal_set_message/spanspan style=color: #007700(/spanspan style=color: #0000BB$e/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetMessage/spanspan style=color: #007700());br /        /spanspan style=color: #0000BBdrupal_goto/spanspan style=color: #007700(/spanspan style=color: #DD0000'user/login'/spanspan style=color: #007700, array(/spanspan style=color: #DD0000'query' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBdrupal_get_destination/spanspan style=color: #007700()));br /      }br /      elseif (/spanspan style=color: #0000BB$e/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetCode/spanspan style=color: #007700() == /spanspan style=color: #0000BBOUR_REST_LOGIN_REQUIRED_TOKEN_EXPIRED/spanspan style=color: #007700) {br /br /        /spanspan style=color: #FF8000// Logoutbr /        /spanspan style=color: #007700global /spanspan style=color: #0000BB$user/spanspan style=color: #007700;br /        /spanspan style=color: #0000BBmodule_invoke_all/spanspan style=color: #007700(/spanspan style=color: #DD0000'user_logout'/spanspan style=color: #007700, /spanspan style=color: #0000BB$user/spanspan style=color: #007700);br /        /spanspan style=color: #0000BBsession_destroy/spanspan style=color: #007700();br /br /        /spanspan style=color: #FF8000// Redirectbr /        /spanspan style=color: #0000BBdrupal_set_message/spanspan style=color: #007700(/spanspan style=color: #0000BB$e/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBgetMessage/spanspan style=color: #007700());br /        /spanspan style=color: #0000BBdrupal_goto/spanspan style=color: #007700(/spanspan style=color: #DD0000'user/login'/spanspan style=color: #007700, array(/spanspan style=color: #DD0000'query' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BBdrupal_get_destination/spanspan style=color: #007700()));br /      }br /    }br /br /    switch(/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBbase_entity_type/spanspan style=color: #007700) {br /      case /spanspan style=color: #DD0000'siteshortname_entities_remote_event' /spanspan style=color: #007700:br /        /spanspan style=color: #0000BB$entities /spanspan style=color: #007700= /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBparseEventResponse/spanspan style=color: #007700(/spanspan style=color: #0000BB$response/spanspan style=color: #007700);br /        break;br /    }br /br /    /spanspan style=color: #FF8000// Return the list of results.br /    /spanspan style=color: #007700return /spanspan style=color: #0000BB$entities/spanspan style=color: #007700;br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=unmarshalling-the-response-data-and-returning-it id=unmarshalling-the-response-data-and-returning-itUnmarshalling the response data and returning it/a/h4 pHere, in the emparseEventResponse/em method, we decode the response data (if there is any), and do any additional work required to get each entity's data into an object. They're all returned as a single list (array) of entity objects. If the response provides information on the format (XML, JSON, etc.), you can unmarshal the data differently based on what the server returned./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**br /   * Helper for execute() which parses the JSON response for event entities.br /   *br /   * May also set the $total_record_count property on the query, if applicable.br /   *br /   * @param $responsebr /   *  The JSON/XML/whatever response from the REST server.br /   *br /   * @returnbr /   *  An list of entity objects, keyed numerically.br /   *  An empty array is returned if the response contains no entities.br /   *br /   * @throwsbr /   *  Exception if a fault is received when the REST call was made.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBparseEventResponse/spanspan style=color: #007700(/spanspan style=color: #0000BB$response/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Fetch the list of events.br /    /spanspan style=color: #007700if (/spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBcode /spanspan style=color: #007700== /spanspan style=color: #0000BB404/spanspan style=color: #007700) {br /      /spanspan style=color: #FF8000// No data was returned so let's provide an empty list.br /      /spanspan style=color: #0000BB$events /spanspan style=color: #007700= array();br /    }br /    else /spanspan style=color: #FF8000/* we have response data */ /spanspan style=color: #007700{br /br /      /spanspan style=color: #FF8000// Convert the JSON (assuming that's what we're getting) into a PHP array.br /      // Do any unmarshalling to convert the response data into a PHP array.br /      /spanspan style=color: #0000BB$events /spanspan style=color: #007700= /spanspan style=color: #0000BBjson_decode/spanspan style=color: #007700(/spanspan style=color: #0000BB$response/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBdata/spanspan style=color: #007700, /spanspan style=color: #0000BBTRUE/spanspan style=color: #007700);br /    }br /br /    /spanspan style=color: #FF8000// Initialize an empty list of entities for returning.br /    /spanspan style=color: #0000BB$entities /spanspan style=color: #007700= array();br /br /    /spanspan style=color: #FF8000// Iterate through each event.br /    /spanspan style=color: #007700foreach (/spanspan style=color: #0000BB$events /spanspan style=color: #007700as /spanspan style=color: #0000BB$event/spanspan style=color: #007700) {br /      /spanspan style=color: #0000BB$entities/spanspan style=color: #007700[] = (object) array(br /br /        /spanspan style=color: #FF8000// Set event information.br /        /spanspan style=color: #DD0000'event_id' /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'id'/spanspan style=color: #007700]) ? /spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'id'/spanspan style=color: #007700] : /spanspan style=color: #0000BBNULL/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'event_name' /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'name'/spanspan style=color: #007700]) ? /spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'name'/spanspan style=color: #007700] : /spanspan style=color: #0000BBNULL/spanspan style=color: #007700,br /        /spanspan style=color: #DD0000'event_date' /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'date'/spanspan style=color: #007700]) ? /spanspan style=color: #0000BB$event/spanspan style=color: #007700[/spanspan style=color: #DD0000'date'/spanspan style=color: #007700] : /spanspan style=color: #0000BBNULL/spanspan style=color: #007700,br /      );br /    }br /br /    /spanspan style=color: #FF8000// Return the newly-created list of entities.br /    /spanspan style=color: #007700return /spanspan style=color: #0000BB$entities/spanspan style=color: #007700;br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /h4a name=error-handling id=error-handlingError handling/a/h4 pWe provide a helper method dealing with errors raised in other methods. It records the specific error message in the log and throws an exception based on the message and the code./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**br /   * Throw an exception when there's a problem.br /   *br /   * @param string $codebr /   *   The error code.br /   *br /   * @param string $messagebr /   *   A user-friendly message describing the problem.br /   *br /   * @throws Exceptionbr /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBthrowException/spanspan style=color: #007700(/spanspan style=color: #0000BB$code/spanspan style=color: #007700, /spanspan style=color: #0000BB$message/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Report error to the logs.br /    /spanspan style=color: #0000BBwatchdog/spanspan style=color: #007700(/spanspan style=color: #DD0000'siteshortname_entities_remote'/spanspan style=color: #007700, /spanspan style=color: #DD0000'ERROR: OurRestRemoteSelectQuery: @code, @message.'/spanspan style=color: #007700, array(br /      /spanspan style=color: #DD0000'@code' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$code/spanspan style=color: #007700,br /      /spanspan style=color: #DD0000'@message' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$message/spanspan style=color: #007700,br /    ));br /br /    /spanspan style=color: #FF8000// Throw an error with which callers must deal.br /   /spanspan style=color: #007700throw new /spanspan style=color: #0000BBException/spanspan style=color: #007700(/spanspan style=color: #0000BBt/spanspan style=color: #007700(/spanspan style=color: #DD0000OurRestRemoteSelectQuery error, got message '@message'./spanspan style=color: #007700, array(br /      /spanspan style=color: #DD0000'@message' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$message/spanspan style=color: #007700,br /    )), /spanspan style=color: #0000BB$code/spanspan style=color: #007700);br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pEverything we've covered so far gets our remote data into Drupal. Below, we'll expose it to Views./p h2a name=views-support id=views-supportViews support/a/h2 h3a name=views-basic-set-up id=views-basic-set-upBasic set-up/a/h3 pAt the beginning of this article, I stated that we required the EntityFieldQuery Views Backend module. This allows us to replace the default Views query back-end, a local SQL database, with one that supports querying entities fetchable through the Remote Entity API. Make sure to add it, emefq_views/em, to your custom remote entity module as a dependency./p pFor the curious, the changes I made to EFQ Views Backend to add this support can be found in the issue a href=https://www.drupal.org/node/2283083Add support for remote entities/a./p pI added official documentation for all of this to the a href=http://cgit.drupalcode.org/remote_entity/tree/README.txtRemote Entity API README/a (via a href=https://www.drupal.org/node/2283135Explain how to integrate remote querying through Views/a). As it may not be obvious, when creating a new view of your remote entities, make sure that the base entity is the EntityFieldQuery version, not simply the entity itself. When selecting the entity type on which to base the view, you should see each entity twice: the standard one (via the default query back-end) and the EFQ version./p pAs stated in the documentation, you need to a add a embuildFromEFQ()/em method to your emRemoteEntityQuery/em subclass (which we went over in the previous section). We'll review why this is necessary and give an example next./p h3a name=converting-from-an-efq id=converting-from-an-efqConverting from an EntityFieldQuery/a/h3 pAs EFQ Views only builds EntityFieldQuery objects, we need to convert that type of query to an instance of our RemoteEntityQuery subclass. If EFQ Views stumbles upon a remote query instead of a local one, it will run the emexecute()/em method on one of these objects instead./p pSo we need to tell our subclass how to generate an instance of itself when provided with an EntityFieldQuery object. The method below handles the conversion, which EFQ Views calls when necessary./p div class=codeblockcodespan style=color: #000000span style=color: #0000BBlt;?phpbr /  /spanspan style=color: #FF8000/**br /   * Build the query from an EntityFieldQuery object.br /   *br /   * To have our query work with Views using the EntityFieldQuery Views module,br /   * which assumes EntityFieldQuery query objects, it's necessary to convertbr /   * from the EFQ so that we may execute this one instead.br /   *br /   * @param $efqbr /   *   The built-up EntityFieldQuery object.br /   *br /   * @returnbr /   *   The current object.  Helpful for chaining methods.br /   */br /  /spanspan style=color: #007700function /spanspan style=color: #0000BBbuildFromEFQ/spanspan style=color: #007700(/spanspan style=color: #0000BB$efq/spanspan style=color: #007700) {br /br /    /spanspan style=color: #FF8000// Copy all of the conditions.br /    /spanspan style=color: #007700foreach (/spanspan style=color: #0000BB$efq/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBpropertyConditions /spanspan style=color: #007700as /spanspan style=color: #0000BB$condition/spanspan style=color: #007700) {br /br /      /spanspan style=color: #FF8000// Handle various conditions in different ways.br /      /spanspan style=color: #007700switch (/spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'column'/spanspan style=color: #007700]) {br /br /        /spanspan style=color: #FF8000// Get the from date.br /        /spanspan style=color: #007700case /spanspan style=color: #DD0000'from_date' /spanspan style=color: #007700:br /          /spanspan style=color: #0000BB$from_date /spanspan style=color: #007700= /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700];br /          /spanspan style=color: #FF8000// Convert the date to the correct format for the REST servicebr /          /spanspan style=color: #0000BB$result /spanspan style=color: #007700= /spanspan style=color: #0000BB$from_date/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBformat/spanspan style=color: #007700(/spanspan style=color: #DD0000'Y/m/d'/spanspan style=color: #007700);br /          /spanspan style=color: #FF8000// The above format() can return FALSE in some cases, so add a checkbr /          /spanspan style=color: #007700if ( /spanspan style=color: #0000BB$result /spanspan style=color: #007700) {br /            /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBfrom_date /spanspan style=color: #007700= /spanspan style=color: #0000BB$result/spanspan style=color: #007700;br /          }br /          break;br /br /        /spanspan style=color: #FF8000// Get the to date.br /        /spanspan style=color: #007700case /spanspan style=color: #DD0000'to_date'/spanspan style=color: #007700:br /          /spanspan style=color: #0000BB$to_date /spanspan style=color: #007700= /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700];br /          /spanspan style=color: #FF8000// Convert the date to the correct format for the REST servicebr /          /spanspan style=color: #0000BB$result /spanspan style=color: #007700= /spanspan style=color: #0000BB$to_date/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBformat/spanspan style=color: #007700(/spanspan style=color: #DD0000'Y/m/d'/spanspan style=color: #007700);br /          /spanspan style=color: #FF8000// The above format() can return FALSE in some cases, so add a checkbr /          /spanspan style=color: #007700if ( /spanspan style=color: #0000BB$result /spanspan style=color: #007700) {br /            /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBto_date /spanspan style=color: #007700= /spanspan style=color: #0000BB$result/spanspan style=color: #007700;br /          }br /          break;br /br /        /spanspan style=color: #FF8000// Get the user ID.br /        /spanspan style=color: #007700case /spanspan style=color: #DD0000'user_id'/spanspan style=color: #007700:br /          /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBuser_id /spanspan style=color: #007700= /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700];br /          break;br /br /        default:br /          /spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBconditions/spanspan style=color: #007700[/spanspan style=color: #0000BB$this/spanspan style=color: #007700-gt;/spanspan style=color: #0000BBremote_base/spanspan style=color: #007700][] = array(br /            /spanspan style=color: #DD0000'field' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'column'/spanspan style=color: #007700],br /            /spanspan style=color: #DD0000'value' /spanspan style=color: #007700=gt; /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'value'/spanspan style=color: #007700],br /            /spanspan style=color: #DD0000'operator' /spanspan style=color: #007700=gt; isset(/spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'operator'/spanspan style=color: #007700]) ? /spanspan style=color: #0000BB$condition/spanspan style=color: #007700[/spanspan style=color: #DD0000'operator'/spanspan style=color: #007700] : /spanspan style=color: #0000BBNULL/spanspan style=color: #007700,br /          );br /          break;br /      }br /    }br /br /    return /spanspan style=color: #0000BB$this/spanspan style=color: #007700;br /  }br //spanspan style=color: #0000BB?gt;/span/span/code/divbr /pThat should be it! You'll now need to spend some time (if you haven't already) getting everything connected as above to fit your specific situation. If you can get these details sorted, you'll then be ready to go./p h2a name=alternatives id=alternativesAlternatives/a/h2 pAt the time of this writing, there appears to be only one alternative to the Remote Entity API (not including custom architectures). It's the a href=https://www.drupal.org/project/wsdataWeb Service Data/a suite. The main difference between the modules is that Web Service Data doesn't store a local cache of remote data; the data is always passed through directly./p pIf this more closely matches what you'd like to do, be aware that there is currently no EntityFieldQuery support:/p blockquoteSupport for EntityFieldQuery (coming soon) will allow developers to make entity field queries with web service data./blockquote pThis is very clearly stated on the main project page, but I wasn't able to find an issue in the queue tracking progress. So if you choose this method, you may have to add EFQ support yourself, or you may not be able to use Views with your remote entities./p h2a name=references id=referencesReferences/a/h2 ulliThe a href=https://www.drupal.org/project/clients_ms_dynamics_soapMS Dynamics Client Connection/a reference implementation/li liThe a href=https://www.drupal.org/node/2034119Programming Drupal 7 Entities/a book/li /ulpemThis article, a href=http://colans.net/blog/integrating-remote-data-drupal-7-and-exposing-it-viewsIntegrating remote data into Drupal 7 and exposing it to Views/a, appeared first on the a href=http://colans.net/Colan Schwartz Consulting Services blog/a./em/p/div/div/divspan rel=schema:url resource=/blog/integrating-remote-data-drupal-7-and-exposing-it-views class=rdf-meta element-hidden/spanspan property=schema:name content=Integrating remote data into Drupal 7 and exposing it to Views class=rdf-meta element-hidden/span

DrupalCon News: Making website magic with the DrupalCon site building track

Mon, 02/16/2015 - 19:45
div class=field field--name-body field--type-text-with-summary field--label-hiddendiv class=field__itemsdiv class=field__item evenpIn honor of this year’s DrupalCon in a href=http://en.wikipedia.org/wiki/HollywoodTinseltown/a, we invite you to indulge in a bit of Drupal movie magic./p pImagine the scene…/p pNARRATORbr / You are about to enter another dimension, a dimension not only of configuration and security but of UI. A journey into a wondrous land of complex sites without custom development. Next stop, the emDrupal Zone!/em/p pTHE SCENEbr / Intl. Acme, Inc. Meeting Room - it is day/p pFADE IN/p/div/div/div

Chromatic: Atomic Drupal Development: Building Pieces Before Pages

Mon, 02/16/2015 - 18:12
div class=field field-name-body field-type-text-with-summary field-label-hidden pimg src=http://blog-media.chromaticsites.com.s3.amazonaws.com/drupal/uploads/2015/02/13/atomic-drupal-development-graphic.svg alt= //p pMany designers are praising the benefits of a href=http://bradfrost.com/blog/post/atomic-web-design/Atomic Design/a. Rather than designing pages, Atomic Design focuses on designing systems of individual, reusable components. Designers aren’t – or at least shouldn’t be – the only ones thinking this way. From content strategy to QA, the entire team must be on the same atomic page./p pDevelopment is one area of a project that stands to benefit the most from this change in thought. Organizing a codebase by individual components keeps developers out of each other’s hair, reducing the code and effort overlap that often occurs when building by page or section. It also makes the codebase much easier to understand and maintain. Developers will know where to find code and how to fix, alter, or extend it, regardless of the original author. After enforcing coding standards, only git’s history will know who wrote what. This all saves time and money./p pBecause there are many ways to do anything in Drupal, building every component with the same approach is crucial. In the Drupal world, this approach is known as “the Drupal way”./p h2Building a component the Drupal way/h2 pIndividual a href=https://www.drupal.org/documentation/blocksblocks/a, a href=https://www.drupal.org/project/panelspanel panes/a, or other a href=http://en.wikipedia.org/wiki/User_interfaceUI/a elements would be examples of a component in Drupal. They are placed into a href=https://www.drupal.org/node/171224regions/a within layouts to build pages. Other pages may use the same component in the same or different regions. A given component may vary across pages, but the design and intended functionality are similar. A simple search form is a good example, but they can be much more complex./p pa href=http://blog-media.chromaticsites.com.s3.amazonaws.com/drupal/uploads/2015/02/13/drupal-components-in-regions.jpgimg src=http://blog-media.chromaticsites.com.s3.amazonaws.com/drupal/uploads/2015/02/13/drupal-components-in-regions.jpg style=width:40%;float:right;margin:5px 0 10px 10px; alt=A screenshot of a CHROMATIC blog article with regions outlined and components highlighted title=A screenshot of a CHROMATIC blog article with regions outlined and components highlighted //a/p pDesign deliverables often arrive as complete pages. If the designers haven’t already, identify the components that each page consists of. Break up the page’s layout into regions and those regions into components. Determine which components live on more than one page and if they vary between them. It also helps to identify different components that share design or functionality with others. It’s important to recognize early if they will be sharing code./p pBefore writing a line of code, determine where in the codebase the component will live. a href=http://chromaticsites.com/blog/how-organize-drupal-features-reduce-merge-conflicts-and-stay-saneOrganize custom modules by content types or sections and add relevant components to the same modules/a. A module exported with a href=https://www.drupal.org/project/featuresFeatures/a should be treated no differently than one created by hand; don’t be afraid to add custom code to them (please do). The end goal is to have all back-end and (most) front-end code for a given component living in the same module./p pemWarning: This article is about to move fast and cover more ground than it should. It will move from back-end to front-end. There are many wonderful resources about each topic covered below, so they will be linked to rather than recreated. This will instead provide a high level overview of how they fit together and will highlight the most important pieces./em/p h3Component containers and placement/h3 pThe most common container for a custom component is a a href=https://www.drupal.org/documentation/blocksblock/a, a href=http://fourword.fourkitchens.com/article/building-custom-blocks-drupal-7created with a series of hooks/a. Contributed modules like a href=https://www.drupal.org/project/contextContext/a can help place them on the page. More complex projects may choose to build pages with the a href=https://www.drupal.org/project/panelsPanels module/a. For pages built with Panels, a href=https://ohthehugemanatee.org/blog/2014/01/03/how-to-create-a-custom-panels-pane/custom panel page plugins/a are a component’s container of choice./p pThe a href=https://www.lullabot.com/blog/article/assembling-pages-drupaldecision between blocks and Context, Panels, or another approach/a is important to make early in the project. It is also important to stick with the same approach for every component. This article will focus less on this decision and more on how to construct the markup within the container of choice./p h3View modes and entity_view()/h3 pIf the component displays information from a a href=https://www.drupal.org/node/717120node/a or a href=https://www.drupal.org/node/1261744another type of entity/a, render it with a a href=https://www.drupal.org/node/1577752view mode/a. View modes can render different information from the same entity in different ways. Among other benefits, this helps display content in similar ways among different components./p pCreate a view mode with a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_entity_info_alter/7code class=prettyprinthook_entity_info_alter()/code/a or with the a href=https://www.drupal.org/project/entity_view_modeEntity view modes/a contributed module. This module also provide template suggestions for each entity type in each view mode. Render an individual piece of information with a view mode inside of a component using a href=http://drupalcontrib.org/api/drupal/contributions!entity!entity.module/function/entity_view/7code class=prettyprintentity_view()/code/a (you’ll need the a href=https://www.drupal.org/project/entityEntity span class=capsAPI/span module/a) or a href=https://api.drupal.org/api/drupal/modules%21node%21node.module/function/node_view/7code class=prettyprintnode_view()/code/a. Alter the entity’s information as needed using a preprocess function and adjust the markup in a template. Those pieces will be discussed later./p pIf a component lists more than one entity or node, build a view with the a href=https://www.drupal.org/project/viewsViews contributed module/a. It is best if the view renders content with view modes using the Format options. Create Views components with the Block (or Content pane for Panels) display(s). Views also provides a href=https://api.drupal.org/api/views/theme%21theme.inc/group/views_templates/7template suggestions/a to further customize the markup of the component. The exported view should live in the same module as the code that customizes it. a href=https://www.drupal.org/node/1343708EntityFieldQuery/a might be worth considering as an alternative to using Views./p h3hook_theme() and render arrays/h3 pIf the component does not display information from an entity, such as a UI element, build it with a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a. Drupal core and contributed modules use a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a to build elements like a href=https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_link/7links/a and a href=https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme_item_list/7item lists/a. This allows other modules to override and alter the information used to render the element. a href=https://www.drupal.org/node/173880Default theme functions and templates can also be overridden to alter their markup/a./p pChoose a name for the element that will identify it throughout the codebase. Outline what information the element will need to build the desired output. Use these decisions to define it using a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a. Again, keep this hook in the same custom module as the rest of the code for the component./p pTo render a a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a implementation, construct a a href=https://www.drupal.org/node/930760render array/a. This array should contain the name of the implementation to render and any data it needs as input. Build and return this array to render the element as markup. The a href=https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme/7code class=prettyprinttheme()/code/a function is a common alternative to render arrays, but it has been deprecated in Drupal 8. There are advantages to using render arrays instead, as explained in a href=https://www.drupal.org/node/930760Render Arrays in Drupal 7/a./p h3Custom templates/h3 pDrupal renders all markup through a href=https://www.drupal.org/node/190815templates/a and a href=https://api.drupal.org/api/drupal/includes%21theme.inc/function/theme/7theme functions/a. Use templates to construct markup instead of theme functions. Doing so makes it easier for front-end developers to build and alter the markup they need./p pTemplates place variables provided by a href=http://drupalcontrib.org/api/drupal/contributions!entity!entity.module/function/entity_view/7code class=prettyprintentity_view()/code/a, render arrays, and preprocess functions into the markup. They should live in the “templates” directory of the same module as the rest of the component’s code. The a href=https://www.drupal.org/node/1089656name of a template will come from theme hook suggestions/a. Underscores get replaced with dashes. Tell a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a about the template for each element it defines./p pa href=https://www.lullabot.com/blog/article/theming-best-practices-garland-gets-cleanupThere should be no logic in the template/a and they should not have to dig deep into Drupal’s objects or arrays. They should only use an code class=prettyprintif/code statement to determine if a variable has a value before printing its markup and value. They can also use a code class=prettyprintforeach/code to loop through an array of data. Further manipulation or function calls should happen in a preprocess function./p h3Preprocess functions/h3 pUse a href=https://www.drupal.org/node/223430preprocess functions/a to extract and manipulate data such as field values and prepare them for the template. They are the middleman between the input and the output./p pPreprocess functions a href=http://themery.com/dgd7/advanced-theming/preprocess-process/implementfollow the naming convention/a of a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a implementations. Common base themes often use Drupal core’s preprocess functions, such as a href=https://api.drupal.org/api/drupal/modules!node!node.module/function/template_preprocess_node/7code class=prettyprinthook_preprocess_node()/code/a, in their template.php file. Keeping all preprocess functions in one file will create a mess in no time. Instead, place preprocess functions in the modules that define the parts their working with. This might be the custom feature that contains the exported content type./p h3jQuery/JavaScript files/h3 pCreate a separate JavaScript file for each component that needs custom JavaScript. Place it in a “js” directory within the module and name the file after the component. Be sure to use the a href=https://www.lullabot.com/blog/article/understanding-javascript-behaviors-drupalDrupal behavior system/a and name the behavior after the module and component./p pa href=http://wearepropeople.com/blog/7-ways-to-add-custom-js-and-css-to-a-page-in-drupalAdd the JavaScript file to each page the component will appear on/a. If the component appears on most pages, it might be best to just add it to every page. This will cause less span class=capsHTTP/span requests with JavaScript aggregation enabled. The a href=https://twitter.com/davereid/status/294554866649542657best way to do so/a is with a href=https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_page_build/7code class=prettyprinthook_page_build()/code/a. JavaScript files can also be attached to entities rendered through view modes within a href=https://api.drupal.org/api/drupal/modules!system!system.api.php/function/hook_entity_view/7code class=prettyprinthook_entity_view()/code/a. The best way to add JavaScript to a a href=https://api.drupal.org/api/drupal/modules%21system%21system.api.php/function/hook_theme/7code class=prettyprinthook_theme()/code/a implementation is by attaching it to the render array./p h3Sass components/h3 pWhen using a a href=https://drupalize.me/videos/what-css-preprocessorspan class=capsCSS/span preprocessor/a like a href=http://sass-lang.com/Sass/a, there isn’t much of a penalty to dividing the span class=capsCSS/span into many files. Create a new a href=http://sass-lang.com/guideSass partial/a for each component and give the file the same name as the component. Keep them in a “components” directory within the Sass folder structure. Unlike all other code mentioned in this article, it is often best to keep all span class=capsCSS/span for these components within the theme. Only keep span class=capsCSS/span that supports the core behavior of the component in the module. Consider what styles should persist if it were a contributed module used with other themes./p pIn the component’s template, base the class names off of the component’s name as well. This makes it easy to find the component’s Sass after inspecting the element in the source. Follow the popular a href=https://bem.info/method/span class=capsBEM/span/a / a href=https://smacss.com/span class=capsSMACSS/span/a / a href=http://www.smashingmagazine.com/2011/12/12/an-introduction-to-object-oriented-css-oocss/span class=capsOOCSS/span/a methodologies from there./p h2Coming up for air/h2 pAs mentioned, there are often endless ways to complete the same task in Drupal. This makes learning best practices difficult and “the Drupal way” will vary in the minds of different experts. The best way to grasp what works best is to start building something with other people and learn from mistakes. The approach outlined in this article aligns with common practice, but mileage will vary per project./p pRegardless of approach, focusing on components before pages will only become more important. Drupal content is already displayed on everything from watches to car dashboards. The web is not made of pages anymore. Designers have begun to embrace this and Drupal developers should too; everyone will benefit!/p /div

Makak Media: Taking BackDrop For A Test Drive

Mon, 02/16/2015 - 17:41
div class=field field-type-filefield field-field-blog-image div class=field-items div class=field-item odd img class=imagefield imagefield-field_blog_image width=470 height=470 title=Backdrop Drupal fork alt=Backdrop src=http://www.makakmedia.co.uk/files/backdrop-logo.png?1424100055 / /div /div /div pSo the first a href=https://backdropcms.org/ target=_blankBackDrop/a release is out there in the wild ready for a quick test drive! We're excited to see where this fork of Drupal 7 leads as we believe it to be a good complementary system to Drupal with a long term future./p pFirst off we checked under the hood to get things configured and found the settings.php file in the root folder, which makes for easier access. Also all those txt files have been removed including the CHANGELOG.txt file, which we remove by default, as it supplies useful info to any hacker out there!/p pNaturally the a href=https://backdropcms.org/installation target=_blankinstallation process/a is very similar to Drupal but with a few less settings giving it a simpler feel./p pUpon installation you're presented with a responsive admin menu with a slightly different structure to the standard Drupal menu. Responsiveness out of the box is great and the new menu again has a simpler look./p pa href=http://www.makakmedia.co.uk/blog/taking-backdrop-test-drive target=_blankread more/a/p

DrupalDare: G-WAN as a static Drupal file server

Mon, 02/16/2015 - 17:26
So now that we have concluded that it's easy to setup distribution of files on a separate subdomain, what about using a completely other web server (or in this case an application server)? Will it blend?

Acquia: Development based on Drupal's Fundamental Particles - Brad Czerniak

Mon, 02/16/2015 - 13:34
div class=form-item form-type-item labelLanguage /label Undefined /div div class=field field-name-body field-type-text-with-summary field-label-hidden div class=field-items div property=content:encoded class=field-item evenp Presenter Brad Czerniak caught my eye with a blog post entitled a href=https://www.commercialprogression.com/post/10-things-i-learned-using-drupal-hackathon10 things I learned using Drupal at a hackathon/a, based on his experiences taking part in the #hackDPL (Detroit Public Library) competitive hackathon. In our podcast interview we talk about that – before moving on to Brad's session about the Drupal development best practices he and his team use at a href=https://www.commercialprogression.comCommercial Progression/a in Michigan./p /div /div /div figure class=field-item even rel= resource=https://www.acquia.com/sites/default/files/jdc_brad_image.png class=field-item even div id=styles-2 class=styles styles-field-image styles-style-scale_width_280 styles-container-image styles-preset-scale_width_280 img typeof=foaf:Image src=https://www.acquia.com/sites/default/files/styles/scale_width_280/public/jdc_brad_image.png?itok=_V2ovT59 alt= title= //div !-- render the title tag as caption -- /figure span property=dc:title content=Development based on Drupal#039;s Fundamental Particles - Brad Czerniak class=rdf-meta element-hidden/span

Annertech: Enlightening - The Dark Art of Solr Search with Drupal

Mon, 02/16/2015 - 12:41
span class=field field-node--title field-name-title field-type-string field-label-hiddenEnlightening - The Dark Art of Solr Search with Drupal/span div class=field field-node--body field-name-body field-type-text-with-summary field-label-hidden div class=field-items div class=field-itemh2Why this blog post?/h2 pOften when I add a search function to a Drupal website using Apache Solr, I'm amazed at how complex some people think this is. Many developers/site builders are of the belief that this is some kind of very-hard-to-master black art. They could not be more wrong./p pSo what I want to contribute back to the Drupal community is an understanding of how Solr works, why/how it differs from Drupal Core Search module, and the benefits Solr has over core search./p/div /div /div

lakshminp.com: The Drupal 8 plugin system - part 2

Mon, 02/16/2015 - 11:38
pWe saw in a href='http://www.lakshminp.com/the-drupal-8-plugin-system-part-1/' part 1/a how plugins help us in writing reusable functionality in Drupal 8. There are a lot of concepts which plugins share in common with services, like:/p ol lilimited scope. Do one thing and do it right. /li liPHP classes which are swappable./li /ol pWhich begs the question, how exactly are plugins different from services? br / If your interface expects implementations to yield the same behaviour, then go for services. Otherwise, you should write it as a plugin. This needs some explaining. br / For instance, if you are creating an interface to store data in a persistent system, like MySQL or MongoDB, then it would be implemented as a service. The codesave()/code function in your interface interface will be implemented differently for both the services, but the behaviour will be the same, i.e., it takes data as input parameters, stores them in the respective data store and returns a success message./p pOn the other hand, if you are creating an image effect, it needs to be a plugin. (It already is. Check a href='https://api.drupal.org/api/drupal/core' !modules!image!src!ImageEffectInterface.php/interface/ImageEffectInterface/8image effects as plugins/a). The core concept of image plugins is to take in an image, apply an effect on it and return the modified image. Different image effects yield different behaviours. An image scaling effect might not produce the same behaviour as that of an image rotating effect. Hence, each of these effects need to be implemented as a plugin. If any module wants to create a new image effect, it needs to write a new plugin by extending the codeImageEffectBase/code class./p h4 id=pluginsusedincorePlugins used in core/h4 pLet's take a look at the major plugin types provided by Drupal 8 core. An example plugin of each plugin types will be the subjects of future blog posts./p ol lipstrongBlocks/strong br / Drupal 8 finally got blocks right. Custom blocks can be created from the codeBlockBase/code class./p/li lipstrongField Types, Field Widgets and Field Formatters/strong br / Check a href='http://www.lakshminp.com/the-drupal-8-plugin-system-part-1/' part 1/a for how this is done in Drupal 8./p/li lipstrongActions/strong br / Drupal 8 allows module developers to perform custom actions by implementing the codeActionBase/code class. Blocking a user, unpublishing a comment, making a node sticky etc. are examples of actions./p/li lipstrongImage Effects/strong br / Image effects are plugins which manipulate an image. You can create new image effects by extending codeImageEffectBase/code. Examples of core image effects are codeCropImageEffect/code and codeScaleImageEffect/code./p/li lipstrongInput filters/strong br / User submitted input is passed through a series of filters before it is persisted in the database or output in HTML. These filters are implemented as plugins by implementing the codeFilterBase/code class./p/li lipstrongEntity Types/strong br / In Drupal parlance, entities are objects that persist content or configuration in the database. Each entity is an instance of an entity type. New entity types can be defined using the annotation discovery mechanism./p/li lipstrongViews related plugins/strong br / A large collection of different plugin types are employed by views during the querying, building and rendering stages. /p/li /ol h4 id=plugindiscoveryPlugin Discovery/h4 pPlugin discovery is the process by which Drupal finds plugins written in your module. Drupal 8 has the following plugin discovery mechanisms:/p ol lipstrongAnnotation based/strong. Plugin classes are a href='http://www.lakshminp.com/annotations-in-drupal-8/' annotated/a and have a directory structure which follows the PSR-4 notation. /p/li lipstrongHooks/strong. Plugin modules need to implement a hook to tell the manager about their plugins./p/li lipstrongYAML files/strong. Plugins are listed in YAML files. Drupal Core uses this method for discovering local tasks and local actions./p/li lipstrongStatic/strong. Plugin classes are registered within the plugin manager class itself. This is useful if other modules should not create new plugins of this type./p/li /ol pAnnotation based discovery is the most popular plugin discovery method in use. We will briefly look at how we create a new plugin type using this method in the next part./p