Chris@0: 'tokens', Chris@0: ]; Chris@0: $hooks['tokens'] = [ Chris@0: 'group' => 'tokens', Chris@0: ]; Chris@0: return $hooks; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Alter the registry of modules implementing a hook. Chris@0: * Chris@0: * This hook is invoked during \Drupal::moduleHandler()->getImplementations(). Chris@0: * A module may implement this hook in order to reorder the implementing Chris@0: * modules, which are otherwise ordered by the module's system weight. Chris@0: * Chris@0: * Note that hooks invoked using \Drupal::moduleHandler->alter() can have Chris@0: * multiple variations(such as hook_form_alter() and hook_form_FORM_ID_alter()). Chris@0: * \Drupal::moduleHandler->alter() will call all such variants defined by a Chris@0: * single module in turn. For the purposes of hook_module_implements_alter(), Chris@0: * these variants are treated as a single hook. Thus, to ensure that your Chris@0: * implementation of hook_form_FORM_ID_alter() is called at the right time, Chris@0: * you will have to change the order of hook_form_alter() implementation in Chris@0: * hook_module_implements_alter(). Chris@0: * Chris@0: * @param $implementations Chris@0: * An array keyed by the module's name. The value of each item corresponds Chris@0: * to a $group, which is usually FALSE, unless the implementation is in a Chris@0: * file named $module.$group.inc. Chris@0: * @param $hook Chris@0: * The name of the module hook being implemented. Chris@0: */ Chris@0: function hook_module_implements_alter(&$implementations, $hook) { Chris@0: if ($hook == 'form_alter') { Chris@0: // Move my_module_form_alter() to the end of the list. Chris@0: // \Drupal::moduleHandler()->getImplementations() Chris@0: // iterates through $implementations with a foreach loop which PHP iterates Chris@0: // in the order that the items were added, so to move an item to the end of Chris@0: // the array, we remove it and then add it. Chris@0: $group = $implementations['my_module']; Chris@0: unset($implementations['my_module']); Chris@0: $implementations['my_module'] = $group; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Alter the information parsed from module and theme .info.yml files. Chris@0: * Chris@18: * This hook is invoked in \Drupal\Core\Extension\ExtensionList::doList(). A Chris@18: * module may implement this hook in order to add to or alter the data generated Chris@18: * by reading the .info.yml file with \Drupal\Core\Extension\InfoParser. Chris@0: * Chris@0: * Using implementations of this hook to make modules required by setting the Chris@0: * $info['required'] key is discouraged. Doing so will slow down the module Chris@0: * installation and uninstallation process. Instead, use Chris@0: * \Drupal\Core\Extension\ModuleUninstallValidatorInterface. Chris@0: * Chris@0: * @param array $info Chris@0: * The .info.yml file contents, passed by reference so that it can be altered. Chris@0: * @param \Drupal\Core\Extension\Extension $file Chris@0: * Full information about the module or theme. Chris@0: * @param string $type Chris@0: * Either 'module' or 'theme', depending on the type of .info.yml file that Chris@0: * was passed. Chris@0: * Chris@0: * @see \Drupal\Core\Extension\ModuleUninstallValidatorInterface Chris@0: */ Chris@0: function hook_system_info_alter(array &$info, \Drupal\Core\Extension\Extension $file, $type) { Chris@0: // Only fill this in if the .info.yml file does not define a 'datestamp'. Chris@0: if (empty($info['datestamp'])) { Chris@0: $info['datestamp'] = $file->getMTime(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform necessary actions before a module is installed. Chris@0: * Chris@0: * @param string $module Chris@0: * The name of the module about to be installed. Chris@0: */ Chris@0: function hook_module_preinstall($module) { Chris@0: mymodule_cache_clear(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform necessary actions after modules are installed. Chris@0: * Chris@0: * This function differs from hook_install() in that it gives all other modules Chris@0: * a chance to perform actions when a module is installed, whereas Chris@0: * hook_install() is only called on the module actually being installed. See Chris@0: * \Drupal\Core\Extension\ModuleInstaller::install() for a detailed description of Chris@0: * the order in which install hooks are invoked. Chris@0: * Chris@0: * This hook should be implemented in a .module file, not in an .install file. Chris@0: * Chris@0: * @param $modules Chris@0: * An array of the modules that were installed. Chris@0: * Chris@0: * @see \Drupal\Core\Extension\ModuleInstaller::install() Chris@0: * @see hook_install() Chris@0: */ Chris@0: function hook_modules_installed($modules) { Chris@0: if (in_array('lousy_module', $modules)) { Chris@0: \Drupal::state()->set('mymodule.lousy_module_compatibility', TRUE); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform setup tasks when the module is installed. Chris@0: * Chris@0: * If the module implements hook_schema(), the database tables will Chris@0: * be created before this hook is fired. Chris@0: * Chris@0: * If the module provides a MODULE.routing.yml or alters routing information Chris@0: * these changes will not be available when this hook is fired. If up-to-date Chris@0: * router information is required, for example to use \Drupal\Core\Url, then Chris@0: * (preferably) use hook_modules_installed() or rebuild the router in the Chris@0: * hook_install() implementation. Chris@0: * Chris@0: * Implementations of this hook are by convention declared in the module's Chris@0: * .install file. The implementation can rely on the .module file being loaded. Chris@0: * The hook will only be called when a module is installed. The module's schema Chris@0: * version will be set to the module's greatest numbered update hook. Because of Chris@0: * this, any time a hook_update_N() is added to the module, this function needs Chris@0: * to be updated to reflect the current version of the database schema. Chris@0: * Chris@0: * See the @link https://www.drupal.org/node/146843 Schema API documentation Chris@0: * @endlink for details on hook_schema and how database tables are defined. Chris@0: * Chris@0: * Note that since this function is called from a full bootstrap, all functions Chris@0: * (including those in modules enabled by the current page request) are Chris@0: * available when this hook is called. Use cases could be displaying a user Chris@0: * message, or calling a module function necessary for initial setup, etc. Chris@0: * Chris@0: * Please be sure that anything added or modified in this function that can Chris@0: * be removed during uninstall should be removed with hook_uninstall(). Chris@0: * Chris@0: * @see hook_schema() Chris@0: * @see \Drupal\Core\Extension\ModuleInstaller::install() Chris@0: * @see hook_uninstall() Chris@0: * @see hook_modules_installed() Chris@0: */ Chris@0: function hook_install() { Chris@0: // Create the styles directory and ensure it's writable. Chris@0: $directory = file_default_scheme() . '://styles'; Chris@18: \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform necessary actions before a module is uninstalled. Chris@0: * Chris@0: * @param string $module Chris@0: * The name of the module about to be uninstalled. Chris@0: */ Chris@0: function hook_module_preuninstall($module) { Chris@0: mymodule_cache_clear(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform necessary actions after modules are uninstalled. Chris@0: * Chris@0: * This function differs from hook_uninstall() in that it gives all other Chris@0: * modules a chance to perform actions when a module is uninstalled, whereas Chris@0: * hook_uninstall() is only called on the module actually being uninstalled. Chris@0: * Chris@0: * It is recommended that you implement this hook if your module stores Chris@0: * data that may have been set by other modules. Chris@0: * Chris@0: * @param $modules Chris@0: * An array of the modules that were uninstalled. Chris@0: * Chris@0: * @see hook_uninstall() Chris@0: */ Chris@0: function hook_modules_uninstalled($modules) { Chris@0: if (in_array('lousy_module', $modules)) { Chris@0: \Drupal::state()->delete('mymodule.lousy_module_compatibility'); Chris@0: } Chris@0: mymodule_cache_rebuild(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Remove any information that the module sets. Chris@0: * Chris@0: * The information that the module should remove includes: Chris@0: * - state that the module has set using \Drupal::state() Chris@0: * - modifications to existing tables Chris@0: * Chris@0: * The module should not remove its entry from the module configuration. Chris@0: * Database tables defined by hook_schema() will be removed automatically. Chris@0: * Chris@0: * The uninstall hook must be implemented in the module's .install file. It Chris@0: * will fire when the module gets uninstalled but before the module's database Chris@0: * tables are removed, allowing your module to query its own tables during Chris@0: * this routine. Chris@0: * Chris@0: * @see hook_install() Chris@0: * @see hook_schema() Chris@0: * @see hook_modules_uninstalled() Chris@0: */ Chris@0: function hook_uninstall() { Chris@0: // Remove the styles directory and generated images. Chris@18: \Drupal::service('file_system')->deleteRecursive(file_default_scheme() . '://styles'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return an array of tasks to be performed by an installation profile. Chris@0: * Chris@0: * Any tasks you define here will be run, in order, after the installer has Chris@0: * finished the site configuration step but before it has moved on to the Chris@0: * final import of languages and the end of the installation. This is invoked Chris@0: * by install_tasks(). You can have any number of custom tasks to perform Chris@0: * during this phase. Chris@0: * Chris@0: * Each task you define here corresponds to a callback function which you must Chris@0: * separately define and which is called when your task is run. This function Chris@0: * will receive the global installation state variable, $install_state, as Chris@0: * input, and has the opportunity to access or modify any of its settings. See Chris@0: * the install_state_defaults() function in the installer for the list of Chris@0: * $install_state settings used by Drupal core. Chris@0: * Chris@0: * At the end of your task function, you can indicate that you want the Chris@0: * installer to pause and display a page to the user by returning any themed Chris@0: * output that should be displayed on that page (but see below for tasks that Chris@0: * use the form API or batch API; the return values of these task functions are Chris@0: * handled differently). You should also use #title within the task Chris@0: * callback function to set a custom page title. For some tasks, however, you Chris@0: * may want to simply do some processing and pass control to the next task Chris@0: * without ending the page request; to indicate this, simply do not send back Chris@0: * a return value from your task function at all. This can be used, for Chris@0: * example, by installation profiles that need to configure certain site Chris@0: * settings in the database without obtaining any input from the user. Chris@0: * Chris@0: * The task function is treated specially if it defines a form or requires Chris@0: * batch processing; in that case, you should return either the form API Chris@0: * definition or batch API array, as appropriate. See below for more Chris@0: * information on the 'type' key that you must define in the task definition Chris@0: * to inform the installer that your task falls into one of those two Chris@0: * categories. It is important to use these APIs directly, since the installer Chris@0: * may be run non-interactively (for example, via a command line script), all Chris@0: * in one page request; in that case, the installer will automatically take Chris@0: * care of submitting forms and processing batches correctly for both types of Chris@0: * installations. You can inspect the $install_state['interactive'] boolean to Chris@0: * see whether or not the current installation is interactive, if you need Chris@0: * access to this information. Chris@0: * Chris@0: * Remember that a user installing Drupal interactively will be able to reload Chris@0: * an installation page multiple times, so you should use \Drupal::state() to Chris@0: * store any data that you may need later in the installation process. Any Chris@0: * temporary state must be removed using \Drupal::state()->delete() before Chris@0: * your last task has completed and control is handed back to the installer. Chris@0: * Chris@0: * @param array $install_state Chris@0: * An array of information about the current installation state. Chris@0: * Chris@0: * @return array Chris@0: * A keyed array of tasks the profile will perform during the final stage of Chris@0: * the installation. Each key represents the name of a function (usually a Chris@0: * function defined by this profile, although that is not strictly required) Chris@0: * that is called when that task is run. The values are associative arrays Chris@0: * containing the following key-value pairs (all of which are optional): Chris@0: * - display_name: The human-readable name of the task. This will be Chris@0: * displayed to the user while the installer is running, along with a list Chris@0: * of other tasks that are being run. Leave this unset to prevent the task Chris@0: * from appearing in the list. Chris@0: * - display: This is a boolean which can be used to provide finer-grained Chris@0: * control over whether or not the task will display. This is mostly useful Chris@0: * for tasks that are intended to display only under certain conditions; Chris@0: * for these tasks, you can set 'display_name' to the name that you want to Chris@0: * display, but then use this boolean to hide the task only when certain Chris@0: * conditions apply. Chris@0: * - type: A string representing the type of task. This parameter has three Chris@0: * possible values: Chris@0: * - normal: (default) This indicates that the task will be treated as a Chris@0: * regular callback function, which does its processing and optionally Chris@0: * returns HTML output. Chris@0: * - batch: This indicates that the task function will return a batch API Chris@0: * definition suitable for batch_set() or an array of batch definitions Chris@0: * suitable for consecutive batch_set() calls. The installer will then Chris@0: * take care of automatically running the task via batch processing. Chris@0: * - form: This indicates that the task function will return a standard Chris@0: * form API definition (and separately define validation and submit Chris@0: * handlers, as appropriate). The installer will then take care of Chris@0: * automatically directing the user through the form submission process. Chris@0: * - run: A constant representing the manner in which the task will be run. Chris@0: * This parameter has three possible values: Chris@0: * - INSTALL_TASK_RUN_IF_NOT_COMPLETED: (default) This indicates that the Chris@0: * task will run once during the installation of the profile. Chris@0: * - INSTALL_TASK_SKIP: This indicates that the task will not run during Chris@0: * the current installation page request. It can be used to skip running Chris@0: * an installation task when certain conditions are met, even though the Chris@0: * task may still show on the list of installation tasks presented to the Chris@0: * user. Chris@0: * - INSTALL_TASK_RUN_IF_REACHED: This indicates that the task will run on Chris@0: * each installation page request that reaches it. This is rarely Chris@0: * necessary for an installation profile to use; it is primarily used by Chris@0: * the Drupal installer for bootstrap-related tasks. Chris@0: * - function: Normally this does not need to be set, but it can be used to Chris@0: * force the installer to call a different function when the task is run Chris@0: * (rather than the function whose name is given by the array key). This Chris@0: * could be used, for example, to allow the same function to be called by Chris@0: * two different tasks. Chris@0: * Chris@0: * @see install_state_defaults() Chris@0: * @see batch_set() Chris@0: * @see hook_install_tasks_alter() Chris@0: * @see install_tasks() Chris@0: */ Chris@0: function hook_install_tasks(&$install_state) { Chris@0: // Here, we define a variable to allow tasks to indicate that a particular, Chris@0: // processor-intensive batch process needs to be triggered later on in the Chris@0: // installation. Chris@0: $myprofile_needs_batch_processing = \Drupal::state()->get('myprofile.needs_batch_processing', FALSE); Chris@0: $tasks = [ Chris@0: // This is an example of a task that defines a form which the user who is Chris@0: // installing the site will be asked to fill out. To implement this task, Chris@0: // your profile would define a function named myprofile_data_import_form() Chris@0: // as a normal form API callback function, with associated validation and Chris@0: // submit handlers. In the submit handler, in addition to saving whatever Chris@0: // other data you have collected from the user, you might also call Chris@0: // \Drupal::state()->set('myprofile.needs_batch_processing', TRUE) if the Chris@0: // user has entered data which requires that batch processing will need to Chris@0: // occur later on. Chris@0: 'myprofile_data_import_form' => [ Chris@0: 'display_name' => t('Data import options'), Chris@0: 'type' => 'form', Chris@0: ], Chris@0: // Similarly, to implement this task, your profile would define a function Chris@0: // named myprofile_settings_form() with associated validation and submit Chris@0: // handlers. This form might be used to collect and save additional Chris@0: // information from the user that your profile needs. There are no extra Chris@0: // steps required for your profile to act as an "installation wizard"; you Chris@0: // can simply define as many tasks of type 'form' as you wish to execute, Chris@0: // and the forms will be presented to the user, one after another. Chris@0: 'myprofile_settings_form' => [ Chris@0: 'display_name' => t('Additional options'), Chris@0: 'type' => 'form', Chris@0: ], Chris@0: // This is an example of a task that performs batch operations. To Chris@0: // implement this task, your profile would define a function named Chris@0: // myprofile_batch_processing() which returns a batch API array definition Chris@0: // that the installer will use to execute your batch operations. Due to the Chris@0: // 'myprofile.needs_batch_processing' variable used here, this task will be Chris@0: // hidden and skipped unless your profile set it to TRUE in one of the Chris@0: // previous tasks. Chris@0: 'myprofile_batch_processing' => [ Chris@0: 'display_name' => t('Import additional data'), Chris@0: 'display' => $myprofile_needs_batch_processing, Chris@0: 'type' => 'batch', Chris@0: 'run' => $myprofile_needs_batch_processing ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, Chris@0: ], Chris@0: // This is an example of a task that will not be displayed in the list that Chris@0: // the user sees. To implement this task, your profile would define a Chris@0: // function named myprofile_final_site_setup(), in which additional, Chris@0: // automated site setup operations would be performed. Since this is the Chris@0: // last task defined by your profile, you should also use this function to Chris@0: // call \Drupal::state()->delete('myprofile.needs_batch_processing') and Chris@0: // clean up the state that was used above. If you want the user to pass Chris@0: // to the final Drupal installation tasks uninterrupted, return no output Chris@0: // from this function. Otherwise, return themed output that the user will Chris@0: // see (for example, a confirmation page explaining that your profile's Chris@0: // tasks are complete, with a link to reload the current page and therefore Chris@0: // pass on to the final Drupal installation tasks when the user is ready to Chris@0: // do so). Chris@0: 'myprofile_final_site_setup' => [], Chris@0: ]; Chris@0: return $tasks; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Alter the full list of installation tasks. Chris@0: * Chris@0: * You can use this hook to change or replace any part of the Drupal Chris@0: * installation process that occurs after the installation profile is selected. Chris@0: * Chris@0: * This hook is invoked on the install profile in install_tasks(). Chris@0: * Chris@0: * @param $tasks Chris@0: * An array of all available installation tasks, including those provided by Chris@0: * Drupal core. You can modify this array to change or replace individual Chris@0: * steps within the installation process. Chris@0: * @param $install_state Chris@0: * An array of information about the current installation state. Chris@0: * Chris@0: * @see hook_install_tasks() Chris@0: * @see install_tasks() Chris@0: */ Chris@0: function hook_install_tasks_alter(&$tasks, $install_state) { Chris@0: // Replace the entire site configuration form provided by Drupal core Chris@0: // with a custom callback function defined by this installation profile. Chris@0: $tasks['install_configure_form']['function'] = 'myprofile_install_configure_form'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Perform a single update between minor versions. Chris@0: * Chris@17: * Hook hook_update_N() can only be used to update between minor versions of a Chris@0: * module. To upgrade between major versions of Drupal (for example, between Chris@0: * Drupal 7 and 8), use the @link migrate Migrate API @endlink instead. Chris@0: * Chris@0: * @section sec_naming Naming and documenting your function Chris@0: * For each change in a module that requires one or more actions to be performed Chris@0: * when updating a site, add a new implementation of hook_update_N() to your Chris@0: * mymodule.install file (assuming mymodule is the machine name of your module). Chris@0: * Implementations of hook_update_N() are named (module name)_update_(number). Chris@0: * The numbers are normally composed of three parts: Chris@0: * - 1 or 2 digits for Drupal core compatibility (Drupal 8, 9, 10, etc.). This Chris@0: * convention must be followed. Chris@0: * - 1 digit for your module's major release version; for example, for 8.x-1.* Chris@0: * use 1, for 8.x-2.* use 2, for Core 8.0.x use 0, and for Core 8.1.x use 1. Chris@0: * This convention is optional but suggested for clarity. Chris@0: * - 2 digits for sequential counting, starting with 01. Note that the x000 Chris@0: * number can never be used: the lowest update number that will be recognized Chris@0: * and run for major version x is x001. Chris@0: * Examples: Chris@0: * - node_update_8001(): The first update for the Drupal 8.0.x version of the Chris@0: * Drupal Core node module. Chris@0: * - mymodule_update_8101(): The first update for your custom or contributed Chris@0: * module's 8.x-1.x versions. Chris@0: * - mymodule_update_8201(): The first update for the 8.x-2.x versions. Chris@0: * Chris@0: * Never renumber update functions. The numeric part of the hook implementation Chris@0: * function is stored in the database to keep track of which updates have run, Chris@0: * so it is important to maintain this information consistently. Chris@0: * Chris@0: * The documentation block preceding this function is stripped of newlines and Chris@0: * used as the description for the update on the pending updates task list, Chris@0: * which users will see when they run the update.php script. Chris@0: * Chris@0: * @section sec_notes Notes about the function body Chris@0: * Writing hook_update_N() functions is tricky. There are several reasons why Chris@0: * this is the case: Chris@0: * - You do not know when updates will be run: someone could be keeping up with Chris@0: * every update and run them when the database and code are in the same state Chris@0: * as when you wrote your update function, or they could have waited until a Chris@0: * few more updates have come out, and run several at the same time. Chris@0: * - You do not know the state of other modules' updates either. Chris@0: * - Other modules can use hook_update_dependencies() to run updates between Chris@0: * your module's updates, so you also cannot count on your functions running Chris@0: * right after one another. Chris@0: * - You do not know what environment your update will run in (which modules Chris@0: * are installed, whether certain hooks are implemented or not, whether Chris@0: * services are overridden, etc.). Chris@0: * Chris@0: * Because of these reasons, you'll need to use care in writing your update Chris@0: * function. Some things to think about: Chris@0: * - Never assume that the database schema is the same when the update will run Chris@0: * as it is when you wrote the update function. So, when updating a database Chris@0: * table or field, put the schema information you want to update to directly Chris@0: * into your function instead of calling your hook_schema() function to Chris@0: * retrieve it (this is one case where the right thing to do is copy and paste Chris@0: * the code). Chris@0: * - Never assume that the configuration schema is the same when the update will Chris@0: * run as it is when you wrote the update function. So, when saving Chris@0: * configuration, use the $has_trusted_data = TRUE parameter so that schema is Chris@0: * ignored, and make sure that the configuration data you are saving matches Chris@0: * the configuration schema at the time when you write the update function Chris@0: * (later updates may change it again to match new schema changes). Chris@0: * - Never assume your field or entity type definitions are the same when the Chris@0: * update will run as they are when you wrote the update function. Always Chris@0: * retrieve the correct version via Chris@0: * \Drupal::entityDefinitionUpdateManager()::getEntityType() or Chris@0: * \Drupal::entityDefinitionUpdateManager()::getFieldStorageDefinition(). When Chris@0: * adding a new definition always replicate it in the update function body as Chris@0: * you would do with a schema definition. Chris@0: * - Never call \Drupal::entityDefinitionUpdateManager()::applyUpdates() in an Chris@0: * update function, as it will apply updates for any module not only yours, Chris@0: * which will lead to unpredictable results. Chris@0: * - Be careful about API functions and especially CRUD operations that you use Chris@0: * in your update function. If they invoke hooks or use services, they may Chris@0: * not behave as expected, and it may actually not be appropriate to use the Chris@0: * normal API functions that invoke all the hooks, use the database schema, Chris@0: * and/or use services in an update function -- you may need to switch to Chris@0: * using a more direct method (database query, etc.). Chris@0: * - In particular, loading, saving, or performing any other CRUD operation on Chris@0: * an entity is never safe to do (they always involve hooks and services). Chris@0: * - Never rebuild the router during an update function. Chris@0: * Chris@0: * The following actions are examples of things that are safe to do during Chris@0: * updates: Chris@0: * - Cache invalidation. Chris@0: * - Using \Drupal::configFactory()->getEditable() and \Drupal::config(), as Chris@0: * long as you make sure that your update data matches the schema, and you Chris@0: * use the $has_trusted_data argument in the save operation. Chris@0: * - Marking a container for rebuild. Chris@0: * - Using the API provided by \Drupal::entityDefinitionUpdateManager() to Chris@0: * update the entity schema based on changes in entity type or field Chris@0: * definitions provided by your module. Chris@0: * Chris@0: * See https://www.drupal.org/node/2535316 for more on writing update functions. Chris@0: * Chris@0: * @section sec_bulk Batch updates Chris@0: * If running your update all at once could possibly cause PHP to time out, use Chris@0: * the $sandbox parameter to indicate that the Batch API should be used for your Chris@0: * update. In this case, your update function acts as an implementation of Chris@0: * callback_batch_operation(), and $sandbox acts as the batch context Chris@0: * parameter. In your function, read the state information from the previous Chris@0: * run from $sandbox (or initialize), run a chunk of updates, save the state in Chris@0: * $sandbox, and set $sandbox['#finished'] to a value between 0 and 1 to Chris@0: * indicate the percent completed, or 1 if it is finished (you need to do this Chris@0: * explicitly in each pass). Chris@0: * Chris@0: * See the @link batch Batch operations topic @endlink for more information on Chris@0: * how to use the Batch API. Chris@0: * Chris@0: * @param array $sandbox Chris@0: * Stores information for batch updates. See above for more information. Chris@0: * Chris@0: * @return string|null Chris@0: * Optionally, update hooks may return a translated string that will be Chris@0: * displayed to the user after the update has completed. If no message is Chris@0: * returned, no message will be presented to the user. Chris@0: * Chris@0: * @throws \Drupal\Core\Utility\UpdateException|PDOException Chris@0: * In case of error, update hooks should throw an instance of Chris@0: * Drupal\Core\Utility\UpdateException with a meaningful message for the user. Chris@0: * If a database query fails for whatever reason, it will throw a Chris@0: * PDOException. Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see batch Chris@0: * @see schemaapi Chris@0: * @see hook_update_last_removed() Chris@0: * @see update_get_update_list() Chris@0: * @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface Chris@0: * @see node_update_8001 Chris@0: * @see system_update_8004 Chris@0: * @see https://www.drupal.org/node/2535316 Chris@0: */ Chris@0: function hook_update_N(&$sandbox) { Chris@0: // For non-batch updates, the signature can simply be: Chris@0: // function hook_update_N() { Chris@0: Chris@0: // Example function body for adding a field to a database table, which does Chris@0: // not require a batch operation: Chris@0: $spec = [ Chris@0: 'type' => 'varchar', Chris@0: 'description' => "New Col", Chris@0: 'length' => 20, Chris@0: 'not null' => FALSE, Chris@0: ]; Chris@0: $schema = Database::getConnection()->schema(); Chris@0: $schema->addField('mytable1', 'newcol', $spec); Chris@0: Chris@0: // Example of what to do if there is an error during your update. Chris@0: if ($some_error_condition_met) { Chris@0: throw new UpdateException('Something went wrong; here is what you should do.'); Chris@0: } Chris@0: Chris@0: // Example function body for a batch update. In this example, the values in Chris@0: // a database field are updated. Chris@0: if (!isset($sandbox['progress'])) { Chris@0: // This must be the first run. Initialize the sandbox. Chris@0: $sandbox['progress'] = 0; Chris@0: $sandbox['current_pk'] = 0; Chris@0: $sandbox['max'] = Database::getConnection()->query('SELECT COUNT(myprimarykey) FROM {mytable1}')->fetchField() - 1; Chris@0: } Chris@0: Chris@0: // Update in chunks of 20. Chris@0: $records = Database::getConnection()->select('mytable1', 'm') Chris@0: ->fields('m', ['myprimarykey', 'otherfield']) Chris@0: ->condition('myprimarykey', $sandbox['current_pk'], '>') Chris@0: ->range(0, 20) Chris@0: ->orderBy('myprimarykey', 'ASC') Chris@0: ->execute(); Chris@0: foreach ($records as $record) { Chris@0: // Here, you would make an update something related to this record. In this Chris@0: // example, some text is added to the other field. Chris@0: Database::getConnection()->update('mytable1') Chris@0: ->fields(['otherfield' => $record->otherfield . '-suffix']) Chris@0: ->condition('myprimarykey', $record->myprimarykey) Chris@0: ->execute(); Chris@0: Chris@0: $sandbox['progress']++; Chris@0: $sandbox['current_pk'] = $record->myprimarykey; Chris@0: } Chris@0: Chris@0: $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); Chris@0: Chris@0: // To display a message to the user when the update is completed, return it. Chris@0: // If you do not want to display a completion message, return nothing. Chris@0: return t('All foo bars were updated with the new suffix'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Executes an update which is intended to update data, like entities. Chris@0: * Chris@0: * These implementations have to be placed in a MODULE.post_update.php file. Chris@0: * Chris@0: * These updates are executed after all hook_update_N() implementations. At this Chris@0: * stage Drupal is already fully repaired so you can use any API as you wish. Chris@0: * Chris@14: * NAME can be arbitrary machine names. In contrast to hook_update_N() the Chris@14: * alphanumeric naming of functions in the file is the only thing which ensures Chris@14: * the execution order of those functions. If update order is mandatory, Chris@14: * you should add numerical prefix to NAME or make it completely numerical. Chris@0: * Chris@0: * Drupal also ensures to not execute the same hook_post_update_NAME() function Chris@0: * twice. Chris@0: * Chris@17: * @section sec_bulk Batch updates Chris@17: * If running your update all at once could possibly cause PHP to time out, use Chris@17: * the $sandbox parameter to indicate that the Batch API should be used for your Chris@17: * update. In this case, your update function acts as an implementation of Chris@17: * callback_batch_operation(), and $sandbox acts as the batch context Chris@17: * parameter. In your function, read the state information from the previous Chris@17: * run from $sandbox (or initialize), run a chunk of updates, save the state in Chris@17: * $sandbox, and set $sandbox['#finished'] to a value between 0 and 1 to Chris@17: * indicate the percent completed, or 1 if it is finished (you need to do this Chris@17: * explicitly in each pass). Chris@17: * Chris@17: * See the @link batch Batch operations topic @endlink for more information on Chris@17: * how to use the Batch API. Chris@17: * Chris@0: * @param array $sandbox Chris@0: * Stores information for batch updates. See above for more information. Chris@0: * Chris@0: * @return string|null Chris@0: * Optionally, hook_post_update_NAME() hooks may return a translated string Chris@0: * that will be displayed to the user after the update has completed. If no Chris@0: * message is returned, no message will be presented to the user. Chris@0: * Chris@0: * @throws \Drupal\Core\Utility\UpdateException|PDOException Chris@0: * In case of error, update hooks should throw an instance of Chris@0: * \Drupal\Core\Utility\UpdateException with a meaningful message for the Chris@0: * user. If a database query fails for whatever reason, it will throw a Chris@0: * PDOException. Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see hook_update_N() Chris@0: */ Chris@0: function hook_post_update_NAME(&$sandbox) { Chris@0: // Example of updating some content. Chris@0: $node = \Drupal\node\Entity\Node::load(123); Chris@0: $node->setTitle('foo'); Chris@0: $node->save(); Chris@0: Chris@0: $result = t('Node %nid saved', ['%nid' => $node->id()]); Chris@0: Chris@0: // Example of disabling blocks with missing condition contexts. Note: The Chris@0: // block itself is in a state which is valid at that point. Chris@0: // @see block_update_8001() Chris@0: // @see block_post_update_disable_blocks_with_missing_contexts() Chris@0: $block_update_8001 = \Drupal::keyValue('update_backup')->get('block_update_8001', []); Chris@0: Chris@0: $block_ids = array_keys($block_update_8001); Chris@0: $block_storage = \Drupal::entityManager()->getStorage('block'); Chris@0: $blocks = $block_storage->loadMultiple($block_ids); Chris@0: /** @var $blocks \Drupal\block\BlockInterface[] */ Chris@0: foreach ($blocks as $block) { Chris@0: // This block has had conditions removed due to an inability to resolve Chris@0: // contexts in block_update_8001() so disable it. Chris@0: Chris@0: // Disable currently enabled blocks. Chris@0: if ($block_update_8001[$block->id()]['status']) { Chris@0: $block->setStatus(FALSE); Chris@0: $block->save(); Chris@0: } Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return an array of information about module update dependencies. Chris@0: * Chris@0: * This can be used to indicate update functions from other modules that your Chris@0: * module's update functions depend on, or vice versa. It is used by the update Chris@0: * system to determine the appropriate order in which updates should be run, as Chris@0: * well as to search for missing dependencies. Chris@0: * Chris@0: * Implementations of this hook should be placed in a mymodule.install file in Chris@0: * the same directory as mymodule.module. Chris@0: * Chris@0: * @return Chris@0: * A multidimensional array containing information about the module update Chris@0: * dependencies. The first two levels of keys represent the module and update Chris@0: * number (respectively) for which information is being returned, and the Chris@0: * value is an array of information about that update's dependencies. Within Chris@0: * this array, each key represents a module, and each value represents the Chris@0: * number of an update function within that module. In the event that your Chris@0: * update function depends on more than one update from a particular module, Chris@0: * you should always list the highest numbered one here (since updates within Chris@0: * a given module always run in numerical order). Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see update_resolve_dependencies() Chris@0: * @see hook_update_N() Chris@0: */ Chris@0: function hook_update_dependencies() { Chris@0: // Indicate that the mymodule_update_8001() function provided by this module Chris@0: // must run after the another_module_update_8003() function provided by the Chris@0: // 'another_module' module. Chris@0: $dependencies['mymodule'][8001] = [ Chris@0: 'another_module' => 8003, Chris@0: ]; Chris@0: // Indicate that the mymodule_update_8002() function provided by this module Chris@0: // must run before the yet_another_module_update_8005() function provided by Chris@0: // the 'yet_another_module' module. (Note that declaring dependencies in this Chris@0: // direction should be done only in rare situations, since it can lead to the Chris@0: // following problem: If a site has already run the yet_another_module Chris@0: // module's database updates before it updates its codebase to pick up the Chris@0: // newest mymodule code, then the dependency declared here will be ignored.) Chris@0: $dependencies['yet_another_module'][8005] = [ Chris@0: 'mymodule' => 8002, Chris@0: ]; Chris@0: return $dependencies; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return a number which is no longer available as hook_update_N(). Chris@0: * Chris@0: * If you remove some update functions from your mymodule.install file, you Chris@0: * should notify Drupal of those missing functions. This way, Drupal can Chris@0: * ensure that no update is accidentally skipped. Chris@0: * Chris@0: * Implementations of this hook should be placed in a mymodule.install file in Chris@0: * the same directory as mymodule.module. Chris@0: * Chris@0: * @return Chris@0: * An integer, corresponding to hook_update_N() which has been removed from Chris@0: * mymodule.install. Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see hook_update_N() Chris@0: */ Chris@0: function hook_update_last_removed() { Chris@0: // We've removed the 8.x-1.x version of mymodule, including database updates. Chris@0: // The next update function is mymodule_update_8200(). Chris@0: return 8103; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provide information on Updaters (classes that can update Drupal). Chris@0: * Chris@0: * Drupal\Core\Updater\Updater is a class that knows how to update various parts Chris@0: * of the Drupal file system, for example to update modules that have newer Chris@0: * releases, or to install a new theme. Chris@0: * Chris@0: * @return Chris@0: * An associative array of information about the updater(s) being provided. Chris@0: * This array is keyed by a unique identifier for each updater, and the Chris@0: * values are subarrays that can contain the following keys: Chris@0: * - class: The name of the PHP class which implements this updater. Chris@0: * - name: Human-readable name of this updater. Chris@0: * - weight: Controls what order the Updater classes are consulted to decide Chris@0: * which one should handle a given task. When an update task is being run, Chris@0: * the system will loop through all the Updater classes defined in this Chris@0: * registry in weight order and let each class respond to the task and Chris@0: * decide if each Updater wants to handle the task. In general, this Chris@0: * doesn't matter, but if you need to override an existing Updater, make Chris@0: * sure your Updater has a lighter weight so that it comes first. Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see drupal_get_updaters() Chris@0: * @see hook_updater_info_alter() Chris@0: */ Chris@0: function hook_updater_info() { Chris@0: return [ Chris@0: 'module' => [ Chris@0: 'class' => 'Drupal\Core\Updater\Module', Chris@0: 'name' => t('Update modules'), Chris@0: 'weight' => 0, Chris@0: ], Chris@0: 'theme' => [ Chris@0: 'class' => 'Drupal\Core\Updater\Theme', Chris@0: 'name' => t('Update themes'), Chris@0: 'weight' => 0, Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Alter the Updater information array. Chris@0: * Chris@0: * An Updater is a class that knows how to update various parts of the Drupal Chris@0: * file system, for example to update modules that have newer releases, or to Chris@0: * install a new theme. Chris@0: * Chris@0: * @param array $updaters Chris@0: * Associative array of updaters as defined through hook_updater_info(). Chris@0: * Alter this array directly. Chris@0: * Chris@0: * @ingroup update_api Chris@0: * Chris@0: * @see drupal_get_updaters() Chris@0: * @see hook_updater_info() Chris@0: */ Chris@0: function hook_updater_info_alter(&$updaters) { Chris@0: // Adjust weight so that the theme Updater gets a chance to handle a given Chris@0: // update task before module updaters. Chris@0: $updaters['theme']['weight'] = -1; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check installation requirements and do status reporting. Chris@0: * Chris@0: * This hook has three closely related uses, determined by the $phase argument: Chris@0: * - Checking installation requirements ($phase == 'install'). Chris@0: * - Checking update requirements ($phase == 'update'). Chris@0: * - Status reporting ($phase == 'runtime'). Chris@0: * Chris@0: * Note that this hook, like all others dealing with installation and updates, Chris@0: * must reside in a module_name.install file, or it will not properly abort Chris@0: * the installation of the module if a critical requirement is missing. Chris@0: * Chris@0: * During the 'install' phase, modules can for example assert that Chris@0: * library or server versions are available or sufficient. Chris@0: * Note that the installation of a module can happen during installation of Chris@0: * Drupal itself (by install.php) with an installation profile or later by hand. Chris@0: * As a consequence, install-time requirements must be checked without access Chris@0: * to the full Drupal API, because it is not available during install.php. Chris@0: * If a requirement has a severity of REQUIREMENT_ERROR, install.php will abort Chris@0: * or at least the module will not install. Chris@0: * Other severity levels have no effect on the installation. Chris@0: * Module dependencies do not belong to these installation requirements, Chris@0: * but should be defined in the module's .info.yml file. Chris@0: * Chris@0: * During installation (when $phase == 'install'), if you need to load a class Chris@0: * from your module, you'll need to include the class file directly. Chris@0: * Chris@0: * The 'runtime' phase is not limited to pure installation requirements Chris@0: * but can also be used for more general status information like maintenance Chris@0: * tasks and security issues. Chris@0: * The returned 'requirements' will be listed on the status report in the Chris@0: * administration section, with indication of the severity level. Chris@0: * Moreover, any requirement with a severity of REQUIREMENT_ERROR severity will Chris@0: * result in a notice on the administration configuration page. Chris@0: * Chris@0: * @param $phase Chris@0: * The phase in which requirements are checked: Chris@0: * - install: The module is being installed. Chris@0: * - update: The module is enabled and update.php is run. Chris@0: * - runtime: The runtime requirements are being checked and shown on the Chris@0: * status report page. Chris@0: * Chris@0: * @return Chris@0: * An associative array where the keys are arbitrary but must be unique (it Chris@0: * is suggested to use the module short name as a prefix) and the values are Chris@0: * themselves associative arrays with the following elements: Chris@0: * - title: The name of the requirement. Chris@0: * - value: The current value (e.g., version, time, level, etc). During Chris@0: * install phase, this should only be used for version numbers, do not set Chris@0: * it if not applicable. Chris@0: * - description: The description of the requirement/status. Chris@0: * - severity: The requirement's result/severity level, one of: Chris@0: * - REQUIREMENT_INFO: For info only. Chris@0: * - REQUIREMENT_OK: The requirement is satisfied. Chris@0: * - REQUIREMENT_WARNING: The requirement failed with a warning. Chris@0: * - REQUIREMENT_ERROR: The requirement failed with an error. Chris@0: */ Chris@0: function hook_requirements($phase) { Chris@0: $requirements = []; Chris@0: Chris@0: // Report Drupal version Chris@0: if ($phase == 'runtime') { Chris@0: $requirements['drupal'] = [ Chris@0: 'title' => t('Drupal'), Chris@0: 'value' => \Drupal::VERSION, Chris@17: 'severity' => REQUIREMENT_INFO, Chris@0: ]; Chris@0: } Chris@0: Chris@0: // Test PHP version Chris@0: $requirements['php'] = [ Chris@0: 'title' => t('PHP'), Chris@0: 'value' => ($phase == 'runtime') ? \Drupal::l(phpversion(), new Url('system.php')) : phpversion(), Chris@0: ]; Chris@0: if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) { Chris@0: $requirements['php']['description'] = t('Your PHP installation is too old. Drupal requires at least PHP %version.', ['%version' => DRUPAL_MINIMUM_PHP]); Chris@0: $requirements['php']['severity'] = REQUIREMENT_ERROR; Chris@0: } Chris@0: Chris@0: // Report cron status Chris@0: if ($phase == 'runtime') { Chris@0: $cron_last = \Drupal::state()->get('system.cron_last'); Chris@0: Chris@0: if (is_numeric($cron_last)) { Chris@0: $requirements['cron']['value'] = t('Last run @time ago', ['@time' => \Drupal::service('date.formatter')->formatTimeDiffSince($cron_last)]); Chris@0: } Chris@0: else { Chris@0: $requirements['cron'] = [ Chris@0: 'description' => t('Cron has not run. It appears cron jobs have not been setup on your system. Check the help pages for configuring cron jobs.', [':url' => 'https://www.drupal.org/cron']), Chris@0: 'severity' => REQUIREMENT_ERROR, Chris@0: 'value' => t('Never run'), Chris@0: ]; Chris@0: } Chris@0: Chris@18: $requirements['cron']['description'] .= ' ' . t('You can run cron manually.', [':cron' => Url::fromRoute('system.run_cron')->toString()]); Chris@0: Chris@0: $requirements['cron']['title'] = t('Cron maintenance tasks'); Chris@0: } Chris@0: Chris@0: return $requirements; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @} End of "addtogroup hooks". Chris@0: */