Web Omelettehttps://www.webomelette.com/planet/feed
Drupal planet feed for Web OmeletteenDrupal 7 ctools content_type plugin with multiple subtypeshttps://www.webomelette.com/drupal-7-ctools-content-type-plugin-multiple-subtypes
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>The <code>content_type</code> ctools plugin is the most used type of ctools plugin in Drupal 7. It allows us to quickly build complex (and configurable) components that can be used in the Panels interface. They are quick to set up, the easiest start being the definition of the <code>$plugin</code> array and the implementation of the plugin render function in a <code>.inc</code> file. Have you ever wondered though what the <code>$subtype</code> parameter of this render function is and what it serves?</p>
<p>Most of the time our content_type plugins only have one type so the <code>$subtype</code> argument is the same name as the plugin (and file name). However, it's possible to have multiple subtypes that have slight (but critical) differences while sharing common functionalities. Not many people are familiar with that. Intrigued? Let's see how they work.</p>
<p>When content_type plugins are being processed (loaded, prepared for use and cached), ctools asks them whether there are any subtypes it would like to define or they are single. By default the latter is true but in order to define variations we can either populate an array of subtype definitions in the main <code>$plugin</code> array or implement a function with a specific naming convention: <code>module_name_plugin_name_content_type_content_types</code>. This callback then needs to return the plugin information for all the subtypes of this plugin.</p>
<p>But since it's easier to show than explain, let's take a look at an example. Imagine you need a simple content_type plugin that outputs data which depends on a certain ctools context. You can define your plugin as such:</p>
<pre class="brush: php">$plugin = array(
'title' =&gt; t('My plugin'),
'description' =&gt; t('My plugin description'),
'category' =&gt; t('My category'),
'required context' =&gt; new ctools_context_required(t('Node'), 'node'),
);
</pre><p>This is a simple example of a plugin that depends on the Node context. But what if you want it to depend on the Node context OR the current User context? In other words, it should work on the <code>node_view</code> page manager template or the <code>user_view</code> one. Or whatever page these contexts are on but nowhere else.</p>
<p>Instead of <code>required context</code> you could use <code>'all contexts' =&gt; true</code>. But this would then pass in to your render function all the available contexts. And this is neither elegant nor a statement of dependency on one of those two contexts. In other words, it will be available on all page manager pages but maybe won't do anything on most and it's up to the render function to handle extra logic for checking the contexts.</p>
<p>This is where plugin subtypes come to help out. Since your render function does the exact same regardless of context (or very similar), you can have a subtype for each. So let's see how that's done.</p>
<p>First, we simplify the main plugin array:</p>
<pre class="brush: php">$plugin = array(
'title' =&gt; t('My plugin'),
'description' =&gt; t('My plugin description'),
'category' =&gt; t('My category'),
);
</pre><p>Then we implement the function that returns the subtypes (following this naming convention):</p>
<pre class="brush: php">function my_module_my_plugin_content_type_content_types() {
return array(
'node' =&gt; array(
'title' =&gt; 'My plugin for nodes',
'required context' =&gt; new ctools_context_required(t('Node'), 'node'),
),
'user' =&gt; array(
'title' =&gt; 'My plugin for users',
'required context' =&gt; new ctools_context_required(t('User'), 'user'),
),
);
}
</pre><p>The subtype machine name is the key in the array and the rest is regular plugin definition as we are used to. In our case we define two, each for their respective dependencies. And with this in place we achieve a number of things.</p>
<p>First, we can add the <code>My plugin for nodes</code> content_type plugin whenever the Node context is available and the <code>My plugin for users</code> when the User context is present. They cannot be used in other cases. Second, we ensure that whatever context is passed to the render function is either a Node or a User (nothing else). This can come in really handy when your context is custom and wraps an object that implements a common interface. Third, the <code>$subtype</code> argument to the render function now will be either <code>node</code> or <code>user</code> which is helpful to maybe slightly fork the functionality depending on the subtype.</p>
<p>Clear the caches and give it a go. Let me know how it works out.</p>
</div></div></div>Tue, 26 Jul 2016 08:05:11 +0000Danny Sipos216 at https://www.webomelette.comhttps://www.webomelette.com/drupal-7-ctools-content-type-plugin-multiple-subtypes#commentsQuery for nodes which reference multiple terms in Drupal 7https://www.webomelette.com/query-nodes-which-reference-multiple-terms-drupal-7
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>Have you ever needed to run a custom query that returns all the nodes which reference two or more taxonomy terms? I have, and most of the time it was very simple because the requirement was for nodes which reference either term, rather than ALL the terms.</p>
<p>When we are dealing with an OR type of select query, it couldn't be easier:</p>
<pre class="brush: php">$query = db_select('node', 'n');
$query-&gt;join('taxonomy_index', 'ti', 'ti.nid = n.nid');
$query-&gt;condition('ti.tid', array(1, 2));
$query-&gt;fields('n', array('nid'));
$result = $query-&gt;execute();
</pre><p>The above example keeps things simple by assuming the term reference maintains an index in the <code>taxonomy_index</code> table and that we already have our term IDs.</p>
<p>Things get a bit more complicated when we are trying to query the nodes which reference both term 1 AND term 2. After banging my head against the wall for a bit, I came up with a solution inspired by how Views generates its query:</p>
<pre class="brush: php">$tids = array(1, 2);
$query = db_select('node', 'n');
foreach ($tids as $key =&gt; $tid) {
$query-&gt;innerJoin('taxonomy_index', 'ti_' . $key, 'ti_' . $key . '.nid = n.nid AND ti_' . $key . '.tid = ' . $tid);
}
foreach ($tids as $key =&gt; $tid) {
$query-&gt;condition('ti_' . $key . '.tid', $tid);
}
$query-&gt;fields('n', array('nid'));
$result = $query-&gt;execute();
</pre><p>So basically the solution is to create a join for each term ID we want to filter by. A crucial part of this is that the join needs to happen on the node ID between the two tables (as expected) but also on the term ID we are using for the filter matching the record in the <code>taxonomy_index</code> table. This will ensure that the two joins are not the same but that they reflect the relation between the node record and each individual term ID. Are you still following? Then, we just add our conditions on the newly created joins.</p>
<h2>Caveats</h2>
<p>This should work, and depending on the size of your dataset, it should not have too much of a performance impact. However, as no joins should be done if not absolutely necessary, investigate the possibility of querying for the nodes that reference the first term and then filtering the rest out using PHP. This can be a viable option if you know that usual result sets are not too big so you are not running <code>array_filter()</code> on 2000 terms. And of course, when possible, cache the query appropriately.</p>
</div></div></div>Tue, 10 May 2016 07:05:41 +0000Danny Sipos213 at https://www.webomelette.comhttps://www.webomelette.com/query-nodes-which-reference-multiple-terms-drupal-7#commentsContent Fixtures for Behat Testing in Drupal 7https://www.webomelette.com/content-fixtures-behat-testing-drupal-7
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p><a href="http://docs.behat.org/en/v2.5/">Behat</a> is a great testing framework for running behaviour driven testing (in BDD) against your site. The <a href="https://github.com/jhedstrom/drupalextension">Drupal extention</a> for Behat allows a tighter integration with Drupal that makes propping up tests for a Drupal site that much faster.</p>
<p>In this article we are going to look at a solution for having dummy content available on a Drupal 7 installation that allows Behat to run its tests against. We are not going to cover the basics of Behat or test driven development so I suggest you check out the resources I mentioned above and others for getting up to speed with those issues if you are not already.</p>
<h2>The problem</h2>
<p>Unlike testing frameworks like Simpletest, Behat runs your tests against the current site (Drupal environment in our case). You may use a CI tool that replicates your production site somewhere on another server and then runs the tests against that instance. However, in many cases you will need to have some dummy content configured in a specific way that validates the functionalities you've built for the site. This is especially true during development. Moreover, you need to also ensure that all cases of your tests can be tested (not just the ones that happen to be triggered by the content of your current production db).</p>
<h2>A handy solution</h2>
<p>One solution is to write scenarios or backgrounds that create said content and remove it after the tests. This is the approach of Simpletest-like integration tests. However, a better (and faster) way is to use the <a href="https://www.drupal.org/project/migrate">Migrate</a> module and create migrations for the dummy content that just needs to <em>be</em> there. With a CSV source for your data, you can very quickly set up some node or other kind of migrations that you can run and rollback whenever you want.</p>
<p>The great part of this approach is that you can then leverage Behat hooks to run your migrations before the test suite starts and roll them back when it ends. Or at any step you want. This allows you to leave the site in a clean state after tests have run and you avoid having to manually run the migrations before and roll them back after.</p>
<h3>A Fixture trait</h3>
<p>Since you may have more than one method related to <em>fixturizing</em> your environment for the tests, you can define a Trait that you can use inside the <code>FeatureContext</code> class of your Behat testing setup. In there you can put all the related methods, such as the ones tagged with the <code>@BeforeSuite</code> and <code>@AfterSuite</code> Behat tags.</p>
<p>Let's see a quick example setup:</p>
<pre class="brush: php">/**
* @BeforeSuite
*/
public static function enableFixtureModules(\Behat\Testwork\Hook\Scope\BeforeSuiteScope $scope) {
module_enable(array('migrate', 'my_example_migration_module'));
}
</pre><p>This method is run whenever the Behat testing suite is started and all it does is enable the relevant modules. The <code>my_example_migration_module</code> module contains our migrations.</p>
<p>It's counterpart that runs at the end of the suite would be this:</p>
<pre class="brush: php">/**
* @AfterSuite
*/
public static function disableFixtureModules(\Behat\Testwork\Hook\Scope\AfterSuiteScope $scope) {
module_disable(array('migrate', 'my_example_migration_module'));
}
</pre><p>The method names are not important as Behat looks at the docblock for the tags that start with the <code>@</code> sign.</p>
<p>Now we have Migrate and our migration module enabled while the test is running. Let's also set up the methods responsible for actually running the migrations.</p>
<pre class="brush: php">/**
* @BeforeFeature @fixtures
*/
public static function runAllMigrations(BeforeFeatureScope $scope) {
$machine_names = self::getAllFixtureMigrations(true);
foreach ($machine_names as $machine_name) {
self::runMigration($machine_name);
}
}
</pre><p>This method is called by Behat before each testing Feature that has the <code>@fixtures</code> tag on it. Its counterpart for when the Feature is done would be this:</p>
<pre class="brush: php">/**
* @AfterFeature @fixtures
*/
public static function revertAllMigrations(AfterFeatureScope $scope) {
$machine_names = self::getAllFixtureMigrations();
self::revertMigrations($machine_names);
}
</pre><p>Let's take a quick look at <code>getAllFixtureMigrations()</code>:</p>
<pre class="brush: php">protected static function getAllFixtureMigrations($register = false) {
if (!module_exists('my_example_migration_module')) {
return array();
}
module_load_include('inc', 'my_example_migration_module', 'my_example_migration_module.migrate');
$migrations = my_example_migration_module_migrate_api();
$machine_names = array();
foreach ($migrations['migrations'] as $name =&gt; $migration) {
$machine_names[] = $name;
}
if ($register) {
migrate_static_registration($machine_names);
}
return $machine_names;
}
</pre><p>In this method we return all the migrations defined in our <code>my_example_migration_module</code> module. Depending on the <code>$register</code> parameter, we also statically register them so they can be run.</p>
<p>Now let's see what <code>runMigration</code> is all about:</p>
<pre class="brush: php">protected static function runMigration($machine_name) {
$migration = Migration::getInstance($machine_name);
$dependencies = $migration-&gt;getHardDependencies();
if ($dependencies) {
foreach ($dependencies as $name) {
self::runMigration($name);
}
}
$migration-&gt;processImport();
}
</pre><p>This method simply loads a migration, runs the import on its dependencies and then on the original migration. This way we ensure that the migration has its dependencies already met.</p>
<p>Finally, let's see what the <code>revertMigrations</code> is all about:</p>
<pre class="brush: php">protected static function revertMigrations($machine_names) {
$dependencies = array();
foreach ($machine_names as $machine_name) {
$migration = Migration::getInstance($machine_name);
$dependencies += $migration-&gt;getDependencies();
}
foreach ($dependencies as $dependency) {
$dependencies[$dependency] = $dependency;
}
// First revert top level migrations (no dependencies)
foreach ($machine_names as $machine_name) {
if (in_array($machine_name, $dependencies)) {
continue;
}
self::revertMigration($machine_name);
}
if ($dependencies) {
self::revertMigrations($dependencies);
}
}
</pre><p>This is is a bit more complex, but not really. First, it loads each migration and creates an array of migrations which are dependencies of others (these need to be reverted last). Then it reverts all the migrations which are not a dependency of any other migration. The method for actually reverting the migration is simple:</p>
<pre class="brush: php">protected static function revertMigration($machine_name) {
$migration = Migration::getInstance($machine_name);
$migration-&gt;processRollback(array('force' =&gt; true));
}
</pre><p>Lastly, though, the <code>revertMigrations()</code> method runs itself again passing the array of migrations which were a dependency. This way they get re-processed and reverted in the right order (migrations that are not a dependency first) until no migrations are left.</p>
<p>And this is pretty much it. We have a rudimentary <em>run-all-migrations-on-this-feature</em> kind of content fixture solution.</p>
<p>You can take it way further though to increase performance and flexibility. You can group your migrations depending on whatever criteria you want, and create various tags that only import and rollback the right migrations (depending on the actual feature being run).</p>
<p>Hope this helps.</p>
</div></div></div>Tue, 08 Dec 2015 08:05:49 +0000Danny Sipos201 at https://www.webomelette.comhttps://www.webomelette.com/content-fixtures-behat-testing-drupal-7#commentsOverriding Queues in Drupal 7. Or How Not to Create Duplicate Queue Itemshttps://www.webomelette.com/overriding-queues-drupal-7-or-how-not-create-duplicate-queue-items
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>Have you ever needed to override a <a href="https://api.drupal.org/api/drupal/modules%21system%21system.queue.inc/group/queue/7">Queue in Drupal 7</a>? For example, the reliable database SystemQueue? It's actually quite easy and very flexible as well. Let me first tell you the use case we are going to work with in this article.</p>
<p>Let's say that we want to make sure that when adding items to a queue, those items don't already exist in the queue table. That's actually a fair request in my opinion. An option would be to make a query and see if the item already exists before delegating its creation to the responsible Queue class. However, we'd have to deal with querying for serialised data and is just not performant.</p>
<p>A better way is to use our own <code>DrupalQueueInterface</code> implementation:</p>
<pre class="brush: php">/**
* Custom implementation of the DrupalQueueInterface
*/
class MyCustomQueue extends SystemQueue {
/**
* Overriding the method to make sure no duplicate queue items get created
* but that the items are updated if the exist.
*/
public function createItem($data) {
$serialized = serialize($data);
$query = db_merge('queue')
-&gt;key(array('name' =&gt; $this-&gt;name, 'data' =&gt; $serialized))
-&gt;fields(array(
'name' =&gt; $this-&gt;name,
'data' =&gt; $serialized,
'created' =&gt; time(),
));
return (bool) $query-&gt;execute();
}
}
</pre><p>As you can see, we are extending from the good ol' <code>SystemQueue</code> but overriding it's <code>createItem()</code> method. Instead of the <code>db_insert()</code> statement with which items were persisted, we are using a <code>db_merge()</code> statement to UPSERT the items. This means that if the items already exit, they get updated. If not, they get created. Which is exactly what we want.</p>
<p>Lastly, we need to make sure this class is being used for our queue. Again the solution is simple. Let's say our queue name is <code>my_custom_queue</code>. When requesting it like so:</p>
<pre class="brush: php">DrupalQueue::get('my_custom_queue');
</pre><p>... Drupal looks in the variables table for a variable called <code>queue_class_my_custom_queue</code>. If it finds it, it will try to instantiate a class that has the name specified as the variable value (if it also implements <code>DrupalQueueInterface</code>). If there is no such variable, it falls back to the default one which is <code>SystemQueue</code>.</p>
<p>So this means that our module needs to create that variable so that when we are requesting this particular queue, we get an instance of our own class. We can do this inside install and uninstall hooks:</p>
<pre class="brush: php">/**
* Implements hook_install().
*/
function my_module_install() {
variable_set('queue_class_my_custom_queue', 'MyCustomQueue');
}
/**
* Implements hook_uninstall().
*/
function my_module_uninstall() {
variable_del('queue_class_my_custom_queue');
}
</pre><p>And that is pretty much it. Now our queue will use the custom class we wrote and we can make sure no items are duplicated.</p>
<p>Hope this helps.</p>
</div></div></div>Mon, 09 Nov 2015 08:02:55 +0000Danny Sipos200 at https://www.webomelette.comhttps://www.webomelette.com/overriding-queues-drupal-7-or-how-not-create-duplicate-queue-items#commentsDrupal 7 post insert hooks as a shutdown functionhttps://www.webomelette.com/drupal-7-post-insert-hooks-shutdown-function
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>Have you ever needed to perform an action <strong>after</strong> a node has been inserted or updated? In this article we are going to see how we can trigger functionality after nodes have been inserted or updated. And no, it's probably not what you think.</p>
<p>By default, there is no hook that provides this in Drupal core. Sure, you have <code>hook_node_insert()</code>, <code>hook_node_update()</code> and <code>hook_node_presave()</code> but all of these are called within the scope of <code>node_save()</code>. This is important because, at this point, we cannot rely on the database for any data that may have been affected by the node save process. As the documentation for <code>hook_node_insert()</code> says:</p>
<blockquote><p>when this hook is invoked, the changes have not yet been written to the database, because a database transaction is still in progress.</p>
</blockquote>
<p>When you have to deal with something totally external to the node in question, this may not be a problem. Heck, you might not even need the database. But there are those one or two cases in which you do need data that has been affected by the process.</p>
<p>For example, when inserting a new node, various actions take place. One of them can be <a href="https://www.drupal.org/project/pathauto">Pathauto</a> doing some magic with the node title and saving a path alias for this node. So if you need to alter this final alias in the scope of <code>hook_node_insert()</code>, you cannot rely on something like <code>path_load()</code> to get the node alias data because it might have not yet been written. I've actually seen it return different things in different cases.</p>
<p>What you can do then is register a function with Drupal that will get called just about when Drupal is finished with the request. It's actually very simple. You just call <a href="https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/drupal_register_shutdown_function/7">drupal_register_shutdown_function()</a> with the name of the function you want to be called. Additionally, you can pass as further arguments whatever it is in your context you want passed to it. It's kinda like subscribing a function to an event.</p>
<p>So now when the request is finishing, we know the <code>node_save()</code> database transaction has completed and we can rely on the data from the database. Using <code>path_load()</code> now will guarantee that the latest path for that node is being loaded.</p>
<p>Hope this helps.</p>
</div></div></div>Tue, 20 Oct 2015 07:01:48 +0000Danny Sipos195 at https://www.webomelette.comhttps://www.webomelette.com/drupal-7-post-insert-hooks-shutdown-function#commentsWebomelette.com gets a new face!https://www.webomelette.com/webomelettecom-gets-new-face
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>If you are a reader of Webomelette.com you probably know it's been a while since any love has been given to this website. I decided recently to right this wrong and release a refreshed version. Lo and behold, the new version of Web Omelette!</p>
<p>I think it looks a bit fresher, crisper and should be a bit more performant as well. Additionally, I fixed some of the problems we had with copying code fragments by using the SyntaxHighlighter plugin for displaying code fences. This should also make them a bit more readable.</p>
<p>Moreover, you'll notice in the listing also external articles I've written for other websites. Feel free to check out those write-ups as well.</p>
<p>Please let me know if you encounter any bugs or issues with the website. I'd very much appreciate that.</p>
</div></div></div>Wed, 30 Sep 2015 07:01:19 +0000Danny Sipos194 at https://www.webomelette.comhttps://www.webomelette.com/webomelettecom-gets-new-face#commentsNew contrib module: Info Pluginshttps://www.webomelette.com/new-contrib-module-info-plugins
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>Today I would like to introduce a new module that I released for the Drupal (developer) community to use and share: <a href="https://www.drupal.org/project/info_plugins">Info Plugins</a>. This is for developers who are tired of implementing a bunch of messy <code>_info</code> hooks (and their corresponding callbacks), lumping together functionality that frankly shouldn't even touch. I'm talking about those ugly switch statements in which we have cases for the various extensions we provide. Don't get me wrong Drupal, I love your hook system.</p>
<p>With <strong>Info Plugins</strong>, much (and progressively more and more) of this becomes cleaner by exposing all those extension points to the cTools plugin system. What I mean by this is that using this module you will be able to declare things like Drupal blocks, filters, field formatters and more, as cTools plugins. This way, all the logic related to one plugin (block, filter or field formatter to continue with the above examples) resides neatly within one single plugin include file.</p>
<p>Let us look at an example a bit more in depth: the Drupal block. Instead of implementing up to 4 hooks (<code>_info</code>, <code>_view</code>, <code>_configure</code> and <code>_save</code>) to manage your blocks, you can now define a new <code>core_block</code> plugin within your cTools plugins folder like so:</p>
<pre class="brush: php">$plugin = array(
'title' =&gt; t('Example block'),
// Your own callback for building the display of the block (called by `hook_block_view()`)
// Leaving this empty, the function name would default to `my_module_example_block_view()`
'view' =&gt; 'my_module_render_example_block',
// Your own callback for building the config form for the block (called by `hook_block_configure()`)
// Leaving this empty, the function name would default to `my_module_example_block_configure()`
'configure' =&gt; 'my_module_configure_example_block',
// Your own callback for saving the config for the block (called by `hook_block_saved()`)
// Leaving this empty, the function name would default to `my_module_example_block_save()`
'save' =&gt; 'my_module_save_example_block',
// ... all other options you may want to pass to `hook_block_info()`
);
/**
* Returns a renderable array that represents the block content, i.e.
* the same as you would return from `hook_block_view()`.
*
* @param $delta
* The block delta
*/
function my_module_render_example_block($delta) {
return array(
'#type' =&gt; 'markup',
'#markup' =&gt; 'My custom example block'
);
}
/**
* Returns a form to be used as the block configuration form, i.e. the same
* as you would return from `hook_block_configure()`
*
* @param $delta
* The block delta
*/
function my_module_configure_example_block($delta) {
$form = array();
$form['my_custom_field'] = array(
'#type' =&gt; 'textfield',
'#title' =&gt; t('My custom block field'),
'#default_value' =&gt; variable_get($delta, '')
);
return $form;
}
/**
* Saves the block configuration, i.e. the same as you would do inside
* `hook_block_save()`
*
* @param $block
* An array of values representing the block with it's configuration, i.e. the
* `$edit` array passed to `hook_block_save()`
*/
function my_module_save_example_block($block) {
variable_set($block['delta'], $block['my_custom_field']);
}
</pre><p>So as you can see, your block definition, render and configuration resides in one file. If you need more blocks, you just define more plugins and you are good to go. There is such a documentation oriented example inside the <code>plugin_examples</code> folder of the module for each available plugin type. Plus, don't forget the <a href="http://cgit.drupalcode.org/info_plugins/plain/README.md?h=7.x-1.x">README.md</a> file.</p>
<p>Once you install the module, you will have only one administration screen at <code>admin/config/info-plugins</code> where you'll be able to select which plugin types you need. For example, if you need to define a custom block, enable the <code>Core Block</code> plugin type. If you need others, enable them as well. But I recommend you keep them disabled if you don't use them so that their logic doesn't get included in memory (all the relevant hooks and what not). My goal here is to keep a very low foot print.</p>
<p>To see which plugin types are available to use, please consult the <a href="https://www.drupal.org/project/info_plugins">project page</a>. And keep checking back because I will add more. And if you want to contribute, please don't hesitate. Take it out for a spin, give it a test and please open issues to let me know of any bugs you find. I will be active on this for a while and want to hammer out all the potential problems before creating a stable release.</p>
<p>Cheers!</p>
</div></div></div>Mon, 10 Aug 2015 13:02:31 +0000Danny Sipos163 at https://www.webomelette.comhttps://www.webomelette.com/new-contrib-module-info-plugins#commentsWrite your own Views Bulk Operations actions in Drupal 7https://www.webomelette.com/write-your-own-views-bulk-operations-actions-drupal-7
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p><a href="https://www.drupal.org/project/views_bulk_operations">Views Bulk Operations (VBO)</a> is a powerful module that leverages Views to allow site administrators to perform bulk operations on multiple entities at once. It does so efficiently by processing the items in batches over multiple requests to avoid timeouts.</p>
<p>Installing the module will already provide you with a host of various actions you can perform in bulk on various entities. You can publish 1000 nodes at once, delete them or even change their author. And these are just a few examples of what you can do.</p>
<p>In this article we are going to look at programatically creating our own action that we can trigger with VBO to affect multiple node entities. Sometimes you need to write your own because the use case does not quite fit in the extensive list of actions the module provides.</p>
<p>For our example, let's assume we have an entity reference field called <code>field_users</code> on both the article and basic page content types. This field can reference whatever user it wants. And the requirement is to be able to bulk update the value of this field on a bunch of nodes of both these node types at once.</p>
<p>Out of the box, VBO provides us with an action to change the value of a field but this doesn't help us in this case. When adding a value to this field via VBO, we are presented with as many instances of the field as different node types are in the selection. And this is not ideal if we want to scale the functionality to more than one content type. What we want is to select a number of nodes and then only once provide a value to this field. So let's see how we can define a custom VBO action for this use case.</p>
<h2>The action</h2>
<p>To define a new action we need to implement <a href="">hook_action_info()</a>:</p>
<pre class="brush: php">/**
* Implements hook_action_info().
*/
function my_module_action_info() {
return array(
'my_module_my_custom_action' =&gt; array(
'type' =&gt; 'entity',
'label' =&gt; t('Add a user to Users field'),
'behavior' =&gt; array('changes_property'),
'configurable' =&gt; TRUE,
'vbo_configurable' =&gt; FALSE,
'triggers' =&gt; array('any'),
),
);
}
</pre><p>With this hook implementation we are defining our own action called <code>my_module_my_custom_action</code> which is available to be triggered on all entity types (because we specified <code>entity</code> for the <code>type</code>) and it acts as a property changer. It is configurable using the default Action API but we don't need any kind of VBO specific configuration. For more information on all the values that you can pass here, feel free to consult the <a href="https://www.drupal.org/node/2052067">documentation page</a> for VBO.</p>
<p>Next, it's time to create the configuration form for this action, namely the form that will be presented to us to select the user we want to add to the <code>field_users</code> reference field:</p>
<pre class="brush: php">function my_module_my_custom_action_form() {
$form = array();
$form['user'] = array(
'#type' =&gt; 'textfield',
'#title' =&gt; t('User'),
'#maxlength' =&gt; 60,
'#autocomplete_path' =&gt; 'user/autocomplete',
'#weight' =&gt; -1,
);
return $form;
}
</pre><p>The function name takes from the machine name of the action suffixed by <code>_form</code> and is responsible for creating and returning a form array. All we need is one field which uses the core <code>user/autocomplete</code> path to load users via Ajax. Simple enough.</p>
<p>So now after we make a bulk selection and choose our action, we'll be prompted with this form to choose the user we want to add to the reference field. It follows to couple it with a submit handler that will save the value into the context of the operation:</p>
<pre class="brush: php">function my_module_my_custom_action_submit($form, &amp;$form_state) {
$uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' =&gt; $form_state['values']['user']))-&gt;fetchField();
return array(
'uid' =&gt; $uid,
);
}
</pre><p>The naming of this function is similar to the previous one except for the suffix being <code>_submit</code> this time around. In it, we load from the database the <code>uid</code> of the user that was referenced in the form field by name and return that inside an array. The latter will then be merged into the <code>$context</code> variable available in the next step.</p>
<p>So it's now time to write the final function which represents this step by adding the selected user to the existing ones in that field across all the selected nodes, regardless of their type:</p>
<pre class="brush: php">function my_module_my_custom_action(&amp;$entity, $context) {
if (!isset($entity-&gt;field_users)) {
return;
}
if (!isset($context['uid'])) {
return;
}
if (!empty($entity-&gt;field_users)) {
foreach ($entity-&gt;field_users[LANGUAGE_NONE] as $ref) {
if ($ref['target_id'] === $context['uid']) {
return;
}
}
}
$user = array(
'target_id' =&gt; $context['uid'],
);
if (!empty($entity-&gt;field_users)) {
$entity-&gt;field_users[LANGUAGE_NONE][] = $user;
return;
}
$entity-&gt;field_users[LANGUAGE_NONE] = array($user);
}
</pre><p>The name of this function is exactly the same as the machine name of the action, the reason for which we prefixed the latter with the module name. As arguments, this function gets the entity object that is being changed (by reference) and the context of the operation.</p>
<p>We start by returning early if the current entity doesn't have our <code>field_users</code> field or if by any chance the <code>uid</code> key is not available inside <code>$context</code>. Then we loop through all the values of the field and return if the selected <code>uid</code> already exists (we don't want to add it twice). And last, we add the selected <code>uid</code> to the list of existing users in the field by taking into account the possibilities that the field can be empty or it can already contain values. After passing through this action, VBO will automatically save the node with the changes for us.</p>
<p>And that is pretty much it. Clearing the cache will make the new action available in the VBO configuration of your view. Adding it will then allow you to select as many nodes as you want, specify a user via the autocomplete field and have that user be added to the <code>field_users</code> field of all those nodes. And the cool thing is that you can select any node you want: if the field doesn't exist on that content type, it will just be skipped gracefully because we are checking for this inside the action logic.</p>
<p>Hope this helps.</p>
</div></div></div>Mon, 03 Aug 2015 07:05:01 +0000Danny Sipos162 at https://www.webomelette.comhttps://www.webomelette.com/write-your-own-views-bulk-operations-actions-drupal-7#commentsGo custom or use a contributed module?https://www.webomelette.com/go-custom-or-use-contributed-module
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>That is the main question. If you came here looking for a definitive answer, I'm afraid you won't find one. What you maybe will find is a discussion I propose on this topic and my two cents on the matter.</p>
<p><strong>Why am I talking about this?</strong></p>
<p>I've been working on a big Drupal website recently which has many contributed and custom modules. One of my recent tasks has been enabling validation of an existent text field used for inputting phone numbers. The client needed this number in a specific format. No problem, a quick regex inside <code>hook_node_validate()</code> should do the trick nicely. But then it got me thinking? Isn't there a module I can use for this instead? Well yes there is: <a href="https://www.drupal.org/project/field_validation">Field validation</a>.</p>
<p>I installed this module which comes with a plethora of validation rules and possibilities. The default phone validation for my country was not matching the client expectation so I had to use a custom expression for that. No problem, achieved basically the same result. But was this the better option under the circumstances? You might say yes because people have been working on this module a long time to perfect it, keep it secure and provide all sorts of goodies under the hood. Not to mention that it's used on over 10.000 websites.</p>
<p>However, the Field Validation module is big. It has a bunch of functionality that allows you to perform all sorts of validation on fields. This is not a bad thing, don't get me wrong. But does my little validation need warrant the installation and loading into memory of so much code? My custom solution was very targeted and took no more than 10 or so lines of code.</p>
<p>One argument would be that yes, because I may need other kinds of validation rules in the future so you can use this module also for those. But I think that being already very far in the lifetime of this website the odds are quite low of that. And even if this is the case, will an extra 2-3 validation needs warrant the use of this module?</p>
<p>On the other hand, you can argue that your custom code is not <em>vetted</em>, is difficult to maintain, can be insecure if you make mistakes and basically represents some non-configurable magic on your site. These can all be true but can also all be false depending on the developer, how they document code and the functionality itself.</p>
<p>I ended up with the custom solution in this case because on this site I really want to introduce new modules only if they bring something major to the table (performance being my concern here). So of course, the choice heavily depends on the actual module you are considering, the website it would go on and the custom code you'd write as an alternative.</p>
<p>Moreover, please do not focus on the actual Field Validation module in this discussion. I am not here to discuss its merits but the whether or not installing <strong>any</strong> such module that serves a tiny purpose is the right way to go. This is mostly a Drupal 7 problem as in D8 we use object oriented practices by which we can have as much code as we want because we only load the necessary parts when needed.</p>
<p>So what do you think? Do you have a general <em>rule</em> when it comes to this decision or you also take it case by case basis? If the latter, what are your criteria for informing your choice? If the former, why is this? I'd love to hear what you have to say.</p>
</div></div></div>Mon, 29 Jun 2015 07:00:55 +0000Danny Sipos161 at https://www.webomelette.comhttps://www.webomelette.com/go-custom-or-use-contributed-module#commentsDrupal 8: custom data on configuration entities using the ThirdPartySettingsInterfacehttps://www.webomelette.com/drupal-8-custom-data-configuration-entities-using-thirdpartysettingsinterface
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even"><p>In this article we are going to look at how to use the <a href="https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Config!Entity!ThirdPartySettingsInterface.php/interface/ThirdPartySettingsInterface/8">ThirdPartySettingsInterface</a> to add some extra data to existing configuration entities. For example, if you ever need to store some config together with a node type or a taxonomy vocabulary, there is a great way to do so using this interface. Today we are going to see an example of this and add an extra field to the menu definition and store the value in this way.</p>
<p>There are a number of steps involved in this process. First, we need to alter the form with which the entity configuration data is added and saved. In the case of the menu entity there are two forms (one for adding and one for editing) so we need to alter them both. We can do something like this:</p>
<pre class="brush: php">/**
* Implements hook_form_alter().
*/
function my_module_form_alter(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id === 'menu_add_form' || $form_id === 'menu_edit_form') {
my_module_alter_menu_forms($form, $form_state, $form_id);
}
}
</pre><p>Inside this general <code>hook_form_alter()</code> implementation we delegate the logic to a custom function if the form is one of the two we need. Alternatively you can also implement <code>hook_form_FORM_ID_alter()</code> for both those forms and delegate from each. That would limit a bit on the function calls. But let's see our custom function:</p>
<pre class="brush: php">/**
* Handles the form alter for the menu_add_form and menu_edit_form forms.
*/
function my_module_alter_menu_forms(&amp;$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$menu = $form_state-&gt;getFormObject()-&gt;getEntity();
$form['my_text_field'] = array(
'#type' =&gt; 'textfield',
'#title' =&gt; t('My text field'),
'#description' =&gt; t('This is some extra data'),
'#default_value' =&gt; $menu-&gt;getThirdPartySetting('my_module', 'my_text_field'),
'#weight' =&gt; 1
);
if (isset($form['links'])) {
$form['links']['#weight'] = 2;
}
$form['#entity_builders'][] = 'my_module_form_menu_add_form_builder';
}
</pre><p>In here we do a couple of things. First, we retrieve the configuration entity object which the form is currently editing. Then, we define a new textfield and add it to the form. Next, we check if the form has menu links on it (meaning that it's probably the edit form) in which case we make its weight higher than one of our new field (just so that the form looks nicer). And last, we add a new <code>#entity_builder</code> to the form which will be triggered when the form is submitted.</p>
<p>The <code>getThirdPartySetting()</code> method on the entity object is provided by the <code>ThirdPartySettingsInterface</code> which all configuration entities have by default if they extend from the <code>ConfigEntityBase</code> class. With this method we simply retrieve a value that is stored as <em>third party</em> for a given module (<code>my_module</code> in this case). It will return NULL if none is set so we don't even need to provide a default in this case.</p>
<p>Let us now turn to our <code>#entity_builder</code> which gets called when the form is submitted and <a href="https://www.drupal.org/node/2420295">is responsible for mapping data to the entity</a>:</p>
<pre class="brush: php">/**
* Entity builder for the menu configuration entity.
*/
function my_module_form_menu_add_form_builder($entity_type, \Drupal\system\Entity\Menu $menu, &amp;$form, \Drupal\Core\Form\FormStateInterface $form_state) {
if ($form_state-&gt;getValue('my_text_field')) {
$menu-&gt;setThirdPartySetting('my_module', 'my_text_field', $form_state-&gt;getValue('my_text_field'));
return;
}
$menu-&gt;unsetThirdPartySetting('my_module', 'my_text_field');
}
</pre><p>Inside we check if our textfield was filled in and set it to the <em>third party setting</em> we can access from the config entity object that is passed as an argument. If the form value is empty we reset the <em>third party setting</em> to remove lingering data in case there is something there.</p>
<p>And that's pretty much it for the business logic. We can clear the cache and try this out by creating/editing a menu and storing new data with it. However, our job is not quite finished. We need to add our <a href="https://www.drupal.org/node/1905070#use">configuration schema</a> so that it becomes translatable. Inside the <code>/config/schema/my_module.schema.yml</code> file of our module we need to add this:</p>
<pre><code>system.menu.*.third_party.my_module:
type: mapping
label: 'My module textfield'
mapping:
my_text_field:
type: text
label: 'My textfield'
</code></pre><p>With this schema definition we are basically appending to the schema of the <code>system.menu</code> config entity by specifying some metadata about the <em>third party settings</em> our module provides. For more information on <a href="https://www.drupal.org/node/1905070">config schemas</a> be sure to check out the docs on Drupal.org.</p>
<p>Now if we reinstall our module and turn on configuration translation, we can translate the values users add to <code>my_text_field</code>. You go to <code>admin/config/regional/config-translation/menu</code>, select a menu and when translating in a different language you see a new <code>Third Party Settings</code> fieldset containing all the translatable values defined in the schema.</p>
<p>Hope this helps.</p>
</div></div></div>Mon, 15 Jun 2015 10:00:33 +0000Danny Sipos160 at https://www.webomelette.comhttps://www.webomelette.com/drupal-8-custom-data-configuration-entities-using-thirdpartysettingsinterface#comments