foldershare-8.x-1.2/foldershare.theme.inc
foldershare.theme.inc
<?php /** * @file * Implements theme handling for the module. */ use Drupal\Core\Render\Element; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Url; use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\foldershare\Constants; use Drupal\foldershare\Utilities\FormatUtilities; use Drupal\foldershare\Entity\FolderShare; /** * Implements hook_theme(). * * This module defines templates: * * - 'foldershare' is used to show an entity and its contents. */ function foldershare_theme() { return [ // Show a portion of a page showing a view of a FolderShare entity. 'foldershare' => [ 'render element' => 'elements' , ], // Show a page containing embedded view that lists root folder // FolderShare entities. 'foldershare_view' => [ 'render element' => 'elements' , ], // Show a table of search results listing FolderShare entities. // The variables defined here match those for a standard 'table', // even though we are using an 'item_list' template. 'item_list__search_results__foldershare_search' => [ 'variables' => [ // Provided by SearchController // ---------------------------- // The raw list of row values generated by the search plugin. 'items' => [], // The context for creating the search results. The 'plugin' key // of the value is the ID of the search plugin used. 'context' => [], // The list type (ignored). 'list_type' => 'ol' , // The text to display if the table is empty. 'empty' => NULL, // Added/used by table template // ---------------------------- // The table title. 'caption' => NULL, // The table header. 'header' => NULL, // The table footer. 'footer' => NULL, // The table rows. 'rows' => NULL, // The column groups. 'colgroups' => NULL, // HTML table attributes. 'attributes' => [], // Whether the table's header is sticky. 'sticky' => FALSE, // Whether the table's columns are responsive. 'responsive' => TRUE, ], ], ]; } /** * Prepares the 'foldershare' template. * * The template is used to show a file or folder on a page. * * The incoming variables are rendered from content created by * the folder's view controller and view builder, and derived * from a FolderShare entity. * * The following variables are set up for the template: * - 'view_mode' = the viewing mode. * - 'item' = the raw item. * - 'url' = the page's URL. * - 'page' = TRUE if a page is being shown. * - 'content' = an array of specific content values. * * The ARIA role is set to 'directory'. * * @param array $variables * An input array, extended by this function, that contains a list * of named variables and their values. */ function template_preprocess_foldershare( array & $variables ) { // // Rendering adds items into the 'elements' variable array. // // Get folder or file // ------------------ // Get the folder or file being processed. $item = $variables [ 'elements' ][ '#foldershare' ]; // // Expose // ------ // Pull out selected values for optional access by a theme. $variables [ 'view_mode' ] = $variables [ 'elements' ][ '#view_mode' ]; $variables [ 'item' ] = $item ; $variables [ 'url' ] = $item ->toUrl( 'canonical' , [ 'language' => $item ->language()])->toString(); // // Flags // ----- // Add flags for state. $variables [ 'page' ] = ( $variables [ 'view_mode' ] === 'full' ); // // Consolidated content // -------------------- // The default template adds all the content at once. $variables [ 'content' ] = []; foreach (Element::children( $variables [ 'elements' ]) as $key ) { $variables [ 'content' ][ $key ] = $variables [ 'elements' ][ $key ]; } // Add ARIA role. $variables [ 'attributes' ][ 'role' ] = 'directory' ; } /** * Prepares for the 'foldershare_view' template. * * The template is used to present a FolderShare entity. * * The incoming variables are rendered from content created by * the folder's view controller and view builder, and derived * from a FolderShare entity. * * The following variables are set up for the template: * - 'content' = an array of specific content values. * * The ARIA role is set to 'directory'. * * @param array $variables * An input array, extended by this function, that contains a list * of named variables and their values. */ function template_preprocess_foldershare_view( array & $variables ) { // // Rendering adds items into the 'elements' variable array. // // // Consolidated content // -------------------- // The default template adds all the content at once. $variables [ 'content' ] = []; foreach (Element::children( $variables [ 'elements' ]) as $key ) { $variables [ 'content' ][ $key ] = $variables [ 'elements' ][ $key ]; } // Add ARIA role. $variables [ 'attributes' ][ 'role' ] = 'directory' ; } /** * Prepares the 'item-list--search-results--foldershare-search' template. * * The template presents a table of search results. * * While the template's name is for an "item list", this implementation * instead creates a table. The template itself is based upon the standard * "table.html.twig" in Drupal core and the variables set up here are for * a table presentation, not a list. * * This switch from list to table is done here because: * * - We want to present search results in a table similar to the tabular view * used for root folders and folder children tables. * * - The Search module's SearchController hard-codes use of an item list-based * theme and provides no hooks to override it. * * - Replacing the Search module's SearchController is probably possible by * replacing the route to it with one to a custom controller, but that * custom controller would invoke SearchController for most of the work, * and then swap out the theme name. And that would *still* leave the issue * of mapping variable names from those intended for an item list to those * needed for a table. We would still need this function. * * Without an override hook or replacing the SearchController, the next best * thing is to define our own template named the way SearchController intends * it to be (i.e. "item_list__search_results__*"), but swapped for a table * template. This preprocessor method then remaps the incoming variables * intended for an item list into table variables for the table template. * * The following list variables are assumed to be incoming: * * - 'items' = an array with one entry per search result. Each entry is an * associative array with keys and values set by the search plugin. See * this module's FolderShareSearch plugin. * * - 'empty' = a message to use if the search has no results. * * - 'list_type' = the style of list. SearchController sets this to 'ol', * but this preprocessor and template ignore it. * * - 'context' = the way the search results were generated. The value is an * associative array with the 'plugin' key set to the name of the search * plugin used. * * The following table variables are created by this preprocessor, or by * the standard table preprocessor that this method invokes: * * - 'caption' = the caption for the table. This preprocessor sets this * to NULL (which disables it). * * - 'colgroups' = an array of table column groups. This preprocessor sets * this to an empty array (which disables it). * * - 'header' = an array of table column names. This preprocessor sets * column names and responsive class names (see below). * * - 'rows' = an array of table rows. Each row is an associative array with * column name keys and cell values (see below). * * - 'footer' = an array of table column values for the footer. This * preprocessor sets this to an empty array (which disables it). * * - 'header_columns' = the number of table columns. This preprocessor * always produces a fixed table (see below). * * - 'sticky' = a TRUE/FALSE flag indicating if the header is 'sticky' (it * stays visible even when scrolling long tables). This preprocessor * leaves this at its default FALSE. * * - 'no_striping' = a TRUE/FALSE flag indicating if even/odd row stripe * styles should be used. This preprocessor leaves this at its default TRUE. * * The table produced always has the following columns: * * - 'Name' = the name of the file or folder, displayed as a link. For * responsiveness, the column has high priority. * * - 'Modified' = the modification date of the file or folder. For * responsiveness, the column has low priority. * * - 'Owner' = the owner of the file or folder, displayed as a link. For * responsiveness, the column has medium priority. * * - 'Size' = the size of the file or folder, in bytes. For * responsiveness, the column has low priority. * * Because search results are always specifically sorted from best to worst, * the table produced here does not support column sorting. * * @param array $variables * An input array, extended by this function, that contains a list * of named variables and their values. * * @see template_preprocess_table() */ function template_preprocess_item_list__search_results__foldershare_search( array & $variables ) { // // A standard 'table' template expects the $variables array to contain: // - 'colgroups' = an array of column groups. // - 'header' = an array of header rows. // - 'footer' = an array of footer rows. // - 'rows' = an array of rows. // - 'no_striping' = a TRUE/FALSE to indicate row striping. // // To get the same functionality of the standard 'table' template, this // code invokes that template's preprocessor. But to do so, the above // variables must be set up first. // // No caption, column groups, or footer. $variables [ 'caption' ] = NULL; $variables [ 'colgroups' ] = NULL; $variables [ 'footer' ] = NULL; // Strip the incoming 'empty' text of HTML tags added by SearchController // under the assumption that the text would be shown by itself, rather // than within a table. if ( empty ( $variables [ 'empty' ]) === FALSE && empty ( $variables [ 'empty' ][ '#markup' ]) === FALSE) { $variables [ 'empty' ][ '#markup' ] = strip_tags ( $variables [ 'empty' ][ '#markup' ]); } // Always responsive. $variables [ 'responsive' ] = TRUE; $variables [ 'attributes' ][ 'class' ][] = 'responsive-enabled' ; $variables [ '#attached' ][ 'library' ][] = 'core/drupal.tableresponsive' ; // Add views classes here and below so that the table is styled the // same way as a view, by default. $variables [ 'attributes' ][ 'class' ][] = 'views-table' ; $variables [ '#attached' ][ 'library' ][] = 'views/views.module' ; // Add this module's library so that default file/folder list styling is // done and file MIME icons are added. The appropriate file classes are // also needed and added below. $variables [ '#attached' ][ 'library' ][] = Constants::LIBRARY_MODULE; // Get the date-time formatter. $dateFormatterName = 'foldershare_date_time' ; $dateFormatter = DateFormat::load( $dateFormatterName ); if ( $dateFormatter === NULL) { $dateFormatterName = 'short' ; $dateFormatter = DateFormat::load( $dateFormatterName ); if ( $dateFormatter === NULL) { $dateFormatterName = '' ; } } $dateFormatterService = \Drupal::service( 'date.formatter' ); // Header has a fixed set of well-known columns. // // Give each column the same ID and class as if the column were generated // by the Views module looking at the corresponding FolderShare fields. // This assists theme styling so that the results table is styled the same // as a view table showing root folders or children of a folder. $variables [ 'header' ] = [ 'name' => [ 'data' => t( 'Name' ), 'id' => [ 'view-name-table-column' ], 'class' => [ 'priority-high' , 'views-align-left' , 'views-field' , 'views-field-name' , ], ], 'modified' => [ 'data' => t( 'Modified' ), 'id' => [ 'view-changed-table-column' ], 'class' => [ 'priority-medium' , 'views-align-left' , 'views-field' , 'views-field-changed' , ], ], 'owner' => [ 'data' => t( 'Owner' ), 'id' => [ 'view-uid-table-column' ], 'class ' => [ 'priority-medium' , 'views-align-left' , 'views-field' , 'views-field-uid' , ], ], 'size' => [ 'data' => t( 'Size' ), 'id' => [ 'view-size-table-column' ], 'class' => [ 'priority-low' , 'views-align-right' , 'views-field' , 'views-field-size' , ], ], ]; // Rows have a fixed set of well-known columns as above. $variables [ 'rows' ] = []; foreach ( $variables [ 'items' ] as $item ) { $result = $item [ '#result' ]; if ( empty ( $result ) === TRUE) { continue ; } // Validate that the row's data is usable. If anything is missing, // something odd is happening and we reject presenting the row. if ( empty ( $result [ 'title' ]) === TRUE || empty ( $result [ 'user' ]) === TRUE || empty ( $result [ 'date' ]) === TRUE || empty ( $result [ 'kind' ]) === TRUE || empty ( $result [ 'mime' ]) === TRUE) { continue ; } // Use the item's kind and MIME type to decide on classes for the // name link. These classes style the link to include a file/folder icon. $kind = $result [ 'kind' ]; $nameClasses = [ 'file' ]; if ( $kind === FolderShare::FOLDER_KIND) { $nameClasses [] = 'file--mime-folder-directory' ; $nameClasses [] = 'file--folder' ; } else { $mime = $result [ 'mime' ]; $fm = strtr ( $mime , [ '/' => '-' , '.' => '-' , ]); $nameClasses [] = 'file--mime-' . $fm . ' ' ; $nameClasses [] = 'file--' . file_icon_class( $mime ); } // Process the given URLs for safety. // // The search should always have provided a 'link' URL for the item. // But we'll be paranoid and check. // // The search may or may not provide a user URL. If the current user // does not have permission to view user profiles, then there won't be // a URL and we need a fallback. // if ( empty ( $result [ 'link' ]) === TRUE) { $nameData = $result [ 'title' ]; } else { $nameUrl = UrlHelper::stripDangerousProtocols( $result [ 'link' ]); $nameData = [ '#type' => 'link' , '#title' => $result [ 'title' ], '#url' => Url::fromUri( $nameUrl ), '#attributes' => [ 'class' => $nameClasses , ], ]; } if ( empty ( $result [ 'userurl' ]) === TRUE) { $userData = $result [ 'user' ]; } else { $userUrl = UrlHelper::stripDangerousProtocols( $result [ 'userurl' ]); $userData = [ '#type' => 'link' , '#title' => $result [ 'user' ], '#url' => Url::fromUri( $userUrl ), ]; } // Format the date. $langcode = '' ; if ( empty ( $result [ 'language' ]) === FALSE) { $langcode = $result [ 'language' ]; } if ( $dateFormatterName !== '' ) { $formattedDate = $dateFormatterService ->format( $result [ 'date' ], $dateFormatterName , '' , NULL, $langcode ); } else { // Cannot find a known formatter. Use a custom format. $formattedDate = $dateFormatterService ->format( $result [ 'date' ], 'custom' , 'M j, Y \a\t g:i A' , NULL, $langcode ); } // Add the row. The inner '#attributes' and 'class' array gives styles for // links to the file/folder and user. The outer 'class' array gives // styles to the <td> tag for the table cell. $variables [ 'rows' ][] = [ 'data' => [ 'name' => [ 'data' => $nameData , 'class' => [ 'views-field' , 'views-align-left' , 'views-field-name' , 'priority-high' , ], ], 'modified' => [ 'data' => $formattedDate , 'class' => [ 'views-field' , 'views-align-left' , 'views-field-changed' , 'priority-medium' , ], ], 'owner' => [ 'data' => $userData , 'class' => [ 'views-field' , 'views-align-left' , 'views-field-uid' , 'priority-medium' , ], ], 'size' => [ 'data' => FormatUtilities::formatBytes( $result [ 'size' ]), 'class' => [ 'views-field' , 'views-align-right' , 'views-field-size' , 'priority-low' , ], ], ], ]; } // Remove irrelevant items passed in by the SearchController, thinking it // was invoking an item list template. This is not critical, but it cleans // things up a bit. unset( $variables [ 'list_type' ]); unset( $variables [ 'items' ]); unset( $variables [ 'context' ]); unset( $variables [ 'wrapper_attributes' ]); unset( $variables [ 'content_attributes' ]); unset( $variables [ 'title_attributes' ]); unset( $variables [ 'title_prefix' ]); unset( $variables [ 'title_suffix' ]); // Add ARIA role. $variables [ 'attributes' ][ 'role' ] = 'directory' ; // Let the standard 'table' preprocessor reformat the content. template_preprocess_table( $variables ); } |