/**
  * The Signature Addon only displays the output in the editable form if it thinks it's in the Admin or a form has been submitted
  *
  * @since 1.17
  *
  * @param string $field_content Always empty. Returning not-empty overrides the input.
  * @param GF_Field $field
  * @param string|array $value If array, it's a field with multiple inputs. If string, single input.
  * @param int $lead_id Lead ID. Always 0 for the `gform_field_input` filter.
  * @param int $form_id Form ID
  *
  * @return string Empty string forces Gravity Forms to use the $_POST values
  */
 function edit_entry_field_input($field_content = '', $field, $value = '', $lead_id = 0, $form_id = 0)
 {
     $context = function_exists('gravityview_get_context') ? gravityview_get_context() : '';
     if ('signature' !== $field->type || 'edit' !== $context) {
         return $field_content;
     }
     // We need to fetch a fresh version of the entry, since the saved entry hasn't refreshed in GV yet.
     $entry = GravityView_View::getInstance()->getCurrentEntry();
     $entry = GFAPI::get_entry($entry['id']);
     $entry_value = rgar($entry, $field->id);
     $_POST["input_{$field->id}"] = $entry_value;
     // Used when Edit Entry form *is* submitted
     $_POST["input_{$form_id}_{$field->id}_signature_filename"] = $entry_value;
     // Used when Edit Entry form *is not* submitted
     return '';
     // Return empty string to force using $_POST values instead
 }
Example #2
0
 /**
  * Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including:
  * 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals',
  * and 'not_contains'
  *
  * @since 1.13 You can define context, which displays/hides based on what's being displayed (single, multiple, edit)
  *
  * @see http://docs.gravityview.co/article/252-gvlogic-shortcode
  * @uses GFFormsModel::matches_operation
  * @since 1.7.5
  *
  * @param string $val1 Left side of comparison
  * @param string $val2 Right side of comparison
  * @param string $operation Type of comparison
  *
  * @return bool True: matches, false: not matches
  */
 public static function matches_operation($val1, $val2, $operation)
 {
     $value = false;
     if ('context' === $val1) {
         $matching_contexts = array($val2);
         // We allow for non-standard contexts.
         switch ($val2) {
             // Check for either single or edit
             case 'singular':
                 $matching_contexts = array('single', 'edit');
                 break;
                 // Use multiple as alias for directory for consistency
             // Use multiple as alias for directory for consistency
             case 'multiple':
                 $matching_contexts = array('directory');
                 break;
         }
         $val1 = in_array(gravityview_get_context(), $matching_contexts) ? $val2 : false;
     }
     switch ($operation) {
         case 'equals':
             $value = GFFormsModel::matches_operation($val1, $val2, 'is');
             break;
         case 'greater_than_or_is':
         case 'greater_than_or_equals':
             $is = GFFormsModel::matches_operation($val1, $val2, 'is');
             $gt = GFFormsModel::matches_operation($val1, $val2, 'greater_than');
             $value = $is || $gt;
             break;
         case 'less_than_or_is':
         case 'less_than_or_equals':
             $is = GFFormsModel::matches_operation($val1, $val2, 'is');
             $gt = GFFormsModel::matches_operation($val1, $val2, 'less_than');
             $value = $is || $gt;
             break;
         case 'not_contains':
             $contains = GFFormsModel::matches_operation($val1, $val2, 'contains');
             $value = !$contains;
             break;
         default:
             $value = GFFormsModel::matches_operation($val1, $val2, $operation);
     }
     return $value;
 }
Example #3
0
 /**
  * Return an array of files prepared for output.
  *
  * Processes files by file type and generates unique output for each.
  *
  * Returns array for each file, with the following keys:
  *
  * `file_path` => The file path of the file, with a line break
  * `html` => The file output HTML formatted
  *
  * @since  1.2
  * @todo  Support `playlist` shortcode for playlist of video/audio
  * @usedby gravityview_get_files_array()
  * @param  string $value    Field value passed by Gravity Forms. String of file URL, or serialized string of file URL array
  * @param  string $gv_class Field class to add to the output HTML
  * @return array           Array of file output, with `file_path` and `html` keys (see comments above)
  */
 static function get_files_array($value, $gv_class)
 {
     $gravityview_view = GravityView_View::getInstance();
     extract($gravityview_view->getCurrentField());
     $output_arr = array();
     // Get an array of file paths for the field.
     $file_paths = rgar($field, 'multipleFiles') ? json_decode($value) : array($value);
     // Process each file path
     foreach ($file_paths as $file_path) {
         // If the site is HTTPS, use HTTPS
         if (function_exists('set_url_scheme')) {
             $file_path = set_url_scheme($file_path);
         }
         // This is from Gravity Forms's code
         $file_path = esc_attr(str_replace(" ", "%20", $file_path));
         // If the field is set to link to the single entry, link to it.
         $link = !empty($field_settings['show_as_link']) ? GravityView_API::entry_link($entry, $field) : $file_path;
         // Get file path information
         $file_path_info = pathinfo($file_path);
         $html_format = NULL;
         $disable_lightbox = false;
         $disable_wrapped_link = false;
         // Is this an image?
         $image = new GravityView_Image(array('src' => $file_path, 'class' => 'gv-image gv-field-id-' . $field_settings['id'], 'alt' => $field_settings['label'], 'width' => gravityview_get_context() === 'single' ? NULL : 250));
         $content = $image->html();
         // The new default content is the image, if it exists. If not, use the file name as the content.
         $content = !empty($content) ? $content : $file_path_info['basename'];
         // If pathinfo() gave us the extension of the file, run the switch statement using that.
         $extension = empty($file_path_info['extension']) ? NULL : strtolower($file_path_info['extension']);
         switch (true) {
             // Audio file
             case in_array($extension, wp_get_audio_extensions()):
                 $disable_lightbox = true;
                 if (shortcode_exists('audio')) {
                     $disable_wrapped_link = true;
                     /**
                      * Modify the settings passed to the `wp_video_shortcode()` function
                      *
                      * @since  1.2
                      * @var array
                      */
                     $audio_settings = apply_filters('gravityview_audio_settings', array('src' => $file_path, 'class' => 'wp-audio-shortcode gv-audio gv-field-id-' . $field_settings['id']));
                     /**
                      * Generate the audio shortcode
                      * @link http://codex.wordpress.org/Audio_Shortcode
                      * @link https://developer.wordpress.org/reference/functions/wp_audio_shortcode/
                      */
                     $content = wp_audio_shortcode($audio_settings);
                 }
                 break;
                 // Video file
             // Video file
             case in_array($extension, wp_get_video_extensions()):
                 $disable_lightbox = true;
                 if (shortcode_exists('video')) {
                     $disable_wrapped_link = true;
                     /**
                      * Modify the settings passed to the `wp_video_shortcode()` function
                      *
                      * @since  1.2
                      * @var array
                      */
                     $video_settings = apply_filters('gravityview_video_settings', array('src' => $file_path, 'class' => 'wp-video-shortcode gv-video gv-field-id-' . $field_settings['id']));
                     /**
                      * Generate the video shortcode
                      * @link http://codex.wordpress.org/Video_Shortcode
                      * @link https://developer.wordpress.org/reference/functions/wp_video_shortcode/
                      */
                     $content = wp_video_shortcode($video_settings);
                 }
                 break;
                 // PDF
             // PDF
             case $extension === 'pdf':
                 // PDF needs to be displayed in an IFRAME
                 $link = add_query_arg(array('TB_iframe' => 'true'), $link);
                 break;
                 // if not image, do not set the lightbox (@since 1.5.3)
             // if not image, do not set the lightbox (@since 1.5.3)
             case !in_array($extension, array('jpg', 'jpeg', 'jpe', 'gif', 'png')):
                 $disable_lightbox = true;
                 break;
         }
         // If using Link to File, override the content.
         // (We do this here so that the $disable_lightbox can be set. Yes, there's a little more processing time, but oh well.)
         if (!empty($field_settings['link_to_file'])) {
             // Force the content to be the file name
             $content = $file_path_info["basename"];
             // Restore the wrapped link
             $disable_wrapped_link = false;
         }
         // Whether to use lightbox or not
         if ($disable_lightbox || empty($gravityview_view->atts['lightbox']) || !empty($field_settings['show_as_link'])) {
             $link_atts = empty($field_settings['show_as_link']) ? "target='_blank'" : '';
             $link_atts = apply_filters('gravityview/fields/fileupload/link_atts', $link_atts, $gravityview_view->getCurrentField());
         } else {
             $link_atts = sprintf("rel='%s-{$entry['id']}' class='thickbox' target='_blank'", $gv_class);
         }
         /**
          * Filter to alter the default behaviour of wrapping images (or image names) with a link to the content object
          *
          * @since 1.5.1
          *
          * @param bool $disable_wrapped_link whether to wrap the content with a link to the content object.
          * @param array $gravityview_view->field_data
          *
          * @see GravityView_API:field_value() for info about $gravityview_view->field_data
          *
          */
         $disable_wrapped_link = apply_filters('gravityview/fields/fileupload/disable_link', $disable_wrapped_link, $gravityview_view->getCurrentField());
         // If the HTML output hasn't been overridden by the switch statement above, use the default format
         if (!empty($content) && empty($disable_wrapped_link)) {
             /**
              * Modify the link text (defaults to the file name)
              *
              * @since 1.7
              *
              * @param string $content The existing anchor content. Could be `<img>` tag, audio/video embed or the file name
              * @param array $field GravityView array of the current field being processed
              */
             $content = apply_filters('gravityview/fields/fileupload/link_content', $content, $gravityview_view->getCurrentField());
             $content = '<a href="' . esc_url_raw($link) . '" ' . $link_atts . '>' . $content . '</a>';
         }
         $output_arr[] = array('file_path' => $file_path, 'content' => $content);
     }
     // End foreach loop
     /**
      * Modify the files array
      *
      * @since 1.7
      *
      * @param array $output_arr Associative array of files {
      *      @type string $file_path The path to the file as stored in Gravity Forms
      *      @type string $content The generated output for the file
      * }
      * @param array $field GravityView array of the current field being processed
      */
     $output_arr = apply_filters('gravityview/fields/fileupload/files_array', $output_arr, $gravityview_view->getCurrentField());
     return $output_arr;
 }
 /**
  * Core function to render a View based on a set of arguments
  *
  * @access public
  * @static
  * @param array $passed_args {
  *
  *      Settings for rendering the View
  *
  *      @type int $id View id
  *      @type int $page_size Number of entries to show per page
  *      @type string $sort_field Form field id to sort
  *      @type string $sort_direction Sorting direction ('ASC' or 'DESC')
  *      @type string $start_date - Ymd
  *      @type string $end_date - Ymd
  *      @type string $class - assign a html class to the view
  *      @type string $offset (optional) - This is the start point in the current data set (0 index based).
  * }
  *
  * @return string|null HTML output of a View, NULL if View isn't found
  */
 public function render_view($passed_args)
 {
     // validate attributes
     if (empty($passed_args['id'])) {
         do_action('gravityview_log_error', '[render_view] Returning; no ID defined.', $passed_args);
         return null;
     }
     // Solve problem when loading content via admin-ajax.php
     // @hack
     if (!$this->getGvOutputData()) {
         do_action('gravityview_log_error', '[render_view] gv_output_data not defined; parsing content.', $passed_args);
         $this->parse_content();
     }
     // Make 100% sure that we're dealing with a properly called situation
     if (!is_object($this->getGvOutputData()) || !is_callable(array($this->getGvOutputData(), 'get_view'))) {
         do_action('gravityview_log_error', '[render_view] gv_output_data not an object or get_view not callable.', $this->getGvOutputData());
         return null;
     }
     $view_id = $passed_args['id'];
     $view_data = $this->getGvOutputData()->get_view($view_id, $passed_args);
     do_action('gravityview_log_debug', '[render_view] View Data: ', $view_data);
     do_action('gravityview_log_debug', '[render_view] Init View. Arguments: ', $passed_args);
     // The passed args were always winning, even if they were NULL.
     // This prevents that. Filters NULL, FALSE, and empty strings.
     $passed_args = array_filter($passed_args, 'strlen');
     //Override shortcode args over View template settings
     $atts = wp_parse_args($passed_args, $view_data['atts']);
     do_action('gravityview_log_debug', '[render_view] Arguments after merging with View settings: ', $atts);
     // It's password protected and you need to log in.
     if (post_password_required($view_id)) {
         do_action('gravityview_log_error', sprintf('[render_view] Returning: View %d is password protected.', $view_id));
         // If we're in an embed or on an archive page, show the password form
         if (get_the_ID() !== $view_id) {
             return get_the_password_form();
         }
         // Otherwise, just get outta here
         return null;
     }
     /**
      * Don't render View if user isn't allowed to see it
      * @since 1.15
      */
     if (is_user_logged_in() && false === GVCommon::has_cap('read_gravityview', $view_id)) {
         return null;
     }
     if ($this->isGravityviewPostType()) {
         /**
          * @filter `gravityview_direct_access` Should Views be directly accessible, or only visible using the shortcode?
          * @see https://codex.wordpress.org/Function_Reference/register_post_type#public
          * @see GravityView_Post_Types::init_post_types
          * @since 1.15.2
          * @param[in,out] boolean `true`: allow Views to be accessible directly. `false`: Only allow Views to be embedded via shortcode. Default: `true`
          * @param int $view_id The ID of the View currently being requested. `0` for general setting
          */
         $direct_access = apply_filters('gravityview_direct_access', true, $view_id);
         $embed_only = !empty($atts['embed_only']);
         if (!$direct_access || $embed_only && !GVCommon::has_cap('read_private_gravityviews')) {
             return __('You are not allowed to view this content.', 'gravityview');
         }
     }
     ob_start();
     /**
      * Set globals for templating
      * @deprecated 1.6.2
      */
     global $gravityview_view;
     $gravityview_view = new GravityView_View($view_data);
     $post_id = !empty($atts['post_id']) ? intval($atts['post_id']) : $this->getPostId();
     $gravityview_view->setPostId($post_id);
     if (!$this->getSingleEntry()) {
         // user requested Directory View
         do_action('gravityview_log_debug', '[render_view] Executing Directory View');
         //fetch template and slug
         $view_slug = apply_filters('gravityview_template_slug_' . $view_data['template_id'], 'table', 'directory');
         do_action('gravityview_log_debug', '[render_view] View template slug: ', $view_slug);
         /**
          * Disable fetching initial entries for views that don't need it (DataTables)
          */
         $get_entries = apply_filters('gravityview_get_view_entries_' . $view_slug, true);
         /**
          * Hide View data until search is performed
          * @since 1.5.4
          */
         if (!empty($atts['hide_until_searched']) && !$this->isSearch()) {
             $gravityview_view->setHideUntilSearched(true);
             $get_entries = false;
         }
         if ($get_entries) {
             if (!empty($atts['sort_columns'])) {
                 // add filter to enable column sorting
                 add_filter('gravityview/template/field_label', array($this, 'add_columns_sort_links'), 100, 3);
             }
             $view_entries = self::get_view_entries($atts, $view_data['form_id']);
             do_action('gravityview_log_debug', sprintf('[render_view] Get Entries. Found %s entries total, showing %d entries', $view_entries['count'], sizeof($view_entries['entries'])));
         } else {
             $view_entries = array('count' => null, 'entries' => null, 'paging' => null);
             do_action('gravityview_log_debug', '[render_view] Not fetching entries because `gravityview_get_view_entries_' . $view_slug . '` is false');
         }
         $gravityview_view->setPaging($view_entries['paging']);
         $gravityview_view->setContext('directory');
         $sections = array('header', 'body', 'footer');
     } else {
         // user requested Single Entry View
         do_action('gravityview_log_debug', '[render_view] Executing Single View');
         do_action('gravityview_render_entry_' . $view_data['id']);
         $entry = $this->getEntry();
         // You are not permitted to view this entry.
         if (empty($entry) || !self::is_entry_approved($entry, $atts)) {
             do_action('gravityview_log_debug', '[render_view] Entry does not exist. This may be because of View filters limiting access.');
             /**
              * @since 1.6
              */
             echo esc_attr(apply_filters('gravityview/render/entry/not_visible', __('You have attempted to view an entry that is not visible or may not exist.', 'gravityview')));
             return null;
         }
         // We're in single view, but the view being processed is not the same view the single entry belongs to.
         // important: do not remove this as it prevents fake attempts of displaying entries from other views/forms
         if ($this->getGvOutputData()->has_multiple_views() && $view_id != $this->get_context_view_id()) {
             do_action('gravityview_log_debug', '[render_view] In single entry view, but the entry does not belong to this View. Perhaps there are multiple views on the page. View ID: ' . $view_id);
             return null;
         }
         //fetch template and slug
         $view_slug = apply_filters('gravityview_template_slug_' . $view_data['template_id'], 'table', 'single');
         do_action('gravityview_log_debug', '[render_view] View single template slug: ', $view_slug);
         //fetch entry detail
         $view_entries['count'] = 1;
         $view_entries['entries'][] = $entry;
         do_action('gravityview_log_debug', '[render_view] Get single entry: ', $view_entries['entries']);
         $back_link_label = isset($atts['back_link_label']) ? $atts['back_link_label'] : null;
         // set back link label
         $gravityview_view->setBackLinkLabel($back_link_label);
         $gravityview_view->setContext('single');
         $sections = array('single');
     }
     // add template style
     self::add_style($view_data['template_id']);
     // Prepare to render view and set vars
     $gravityview_view->setEntries($view_entries['entries']);
     $gravityview_view->setTotalEntries($view_entries['count']);
     // If Edit
     if ('edit' === gravityview_get_context()) {
         do_action('gravityview_log_debug', '[render_view] Edit Entry ');
         do_action('gravityview_edit_entry', $this->getGvOutputData());
         return ob_get_clean();
     } else {
         // finaly we'll render some html
         $sections = apply_filters('gravityview_render_view_sections', $sections, $view_data['template_id']);
         do_action('gravityview_log_debug', '[render_view] Sections to render: ', $sections);
         foreach ($sections as $section) {
             do_action('gravityview_log_debug', '[render_view] Rendering ' . $section . ' section.');
             $gravityview_view->render($view_slug, $section, false);
         }
     }
     //@todo: check why we need the IF statement vs. print the view id always.
     if ($this->isGravityviewPostType() || $this->isPostHasShortcode()) {
         // Print the View ID to enable proper cookie pagination
         echo '<input type="hidden" class="gravityview-view-id" value="' . esc_attr($view_id) . '">';
     }
     $output = ob_get_clean();
     return $output;
 }
Example #5
0
    /**
     *
     * @param $view_id
     */
    public function render_widget_hooks($view_id)
    {
        if (empty($view_id) || 'single' == gravityview_get_context()) {
            return;
        }
        $view_data = gravityview_get_current_view_data($view_id);
        // TODO: Move to sep. method, use an action instead
        wp_enqueue_style('gravityview_default_style');
        // get View widget configuration
        $widgets = $view_data['widgets'];
        $rows = GravityView_Plugin::get_default_widget_areas();
        switch (current_filter()) {
            case 'gravityview_before':
                $zone = 'header';
                break;
            case 'gravityview_after':
                $zone = 'footer';
                break;
        }
        // Prevent being called twice
        if (did_action($zone . '_' . $view_id . '_widgets')) {
            return;
        }
        // TODO Convert to partials
        ?>
		<div class="gv-grid">
			<?php 
        foreach ($rows as $row) {
            foreach ($row as $col => $areas) {
                $column = $col == '2-2' ? '1-2 gv-right' : $col . ' gv-left';
                ?>
					<div class="gv-grid-col-<?php 
                echo esc_attr($column);
                ?>
">
						<?php 
                if (!empty($areas)) {
                    foreach ($areas as $area) {
                        if (!empty($widgets[$zone . '_' . $area['areaid']])) {
                            foreach ($widgets[$zone . '_' . $area['areaid']] as $widget) {
                                do_action("gravityview_render_widget_{$widget['id']}", $widget);
                            }
                        }
                    }
                }
                ?>
					</div>
				<?php 
            }
            // $row
            ?>
			<?php 
        }
        // $rows
        ?>
		</div>

		<?php 
        // Prevent being called twice
        do_action($zone . '_' . $view_id . '_widgets');
    }
Example #6
0
    /**
     *
     * @param $view_id
     */
    public function render_widget_hooks($view_id)
    {
        if (empty($view_id) || 'single' == gravityview_get_context()) {
            do_action('gravityview_log_debug', __METHOD__ . ' - Not rendering widgets; single entry');
            return;
        }
        $view_data = gravityview_get_current_view_data($view_id);
        // TODO: Move to sep. method, use an action instead
        wp_enqueue_style('gravityview_default_style');
        // get View widget configuration
        $widgets = $view_data['widgets'];
        $rows = GravityView_Plugin::get_default_widget_areas();
        switch (current_filter()) {
            case 'gravityview_before':
                $zone = 'header';
                break;
            case 'gravityview_after':
                $zone = 'footer';
                break;
        }
        // Prevent being called twice
        if (did_action($zone . '_' . $view_id . '_widgets')) {
            do_action('gravityview_log_debug', sprintf('%s - Not rendering %s; already rendered', __METHOD__, $zone . '_' . $view_id . '_widgets'));
            return;
        }
        // TODO Convert to partials
        ?>
		<div class="gv-grid">
			<?php 
        foreach ($rows as $row) {
            foreach ($row as $col => $areas) {
                $column = $col == '2-2' ? '1-2 gv-right' : $col . ' gv-left';
                ?>
					<div class="gv-grid-col-<?php 
                echo esc_attr($column);
                ?>
">
						<?php 
                if (!empty($areas)) {
                    foreach ($areas as $area) {
                        if (!empty($widgets[$zone . '_' . $area['areaid']])) {
                            foreach ($widgets[$zone . '_' . $area['areaid']] as $widget) {
                                do_action("gravityview_render_widget_{$widget['id']}", $widget);
                            }
                        }
                    }
                }
                ?>
					</div>
				<?php 
            }
            // $row
            ?>
			<?php 
        }
        // $rows
        ?>
		</div>

		<?php 
        /**
         * Prevent widgets from being called twice.
         * Checking for loop_start prevents themes and plugins that pre-process shortcodes from triggering the action before displaying. Like, ahem, the Divi theme and WordPress SEO plugin
         */
        if (did_action('loop_start')) {
            do_action($zone . '_' . $view_id . '_widgets');
        }
    }
Example #7
0
    /**
     *
     * @param $view_id
     */
    public function render_widget_hooks($view_id)
    {
        if (empty($view_id) || 'single' == gravityview_get_context()) {
            do_action('gravityview_log_debug', __METHOD__ . ' - Not rendering widgets; single entry');
            return;
        }
        $view_data = gravityview_get_current_view_data($view_id);
        // get View widget configuration
        $widgets = (array) $view_data['widgets'];
        switch (current_filter()) {
            default:
            case 'gravityview_before':
                $zone = 'header';
                break;
            case 'gravityview_after':
                $zone = 'footer';
                break;
        }
        /**
         * Filter widgets not in the current zone
         * @since 1.16
         */
        foreach ($widgets as $key => $widget) {
            // The widget isn't in the current zone
            if (false === strpos($key, $zone)) {
                unset($widgets[$key]);
            }
        }
        /**
         * Prevent output if no widgets to show.
         * @since 1.16
         */
        if (empty($widgets)) {
            do_action('gravityview_log_debug', sprintf('No widgets for View #%s', $view_id));
            return;
        }
        // Prevent being called twice
        if (did_action($zone . '_' . $view_id . '_widgets')) {
            do_action('gravityview_log_debug', sprintf('%s - Not rendering %s; already rendered', __METHOD__, $zone . '_' . $view_id . '_widgets'));
            return;
        }
        $rows = GravityView_Plugin::get_default_widget_areas();
        // TODO: Move to sep. method, use an action instead
        wp_enqueue_style('gravityview_default_style');
        $default_css_class = 'gv-grid gv-widgets-' . $zone;
        if (0 === GravityView_View::getInstance()->getTotalEntries()) {
            $default_css_class .= ' gv-widgets-no-results';
        }
        /**
         * @filter `gravityview/widgets/wrapper_css_class` The CSS class applied to the widget container `<div>`.
         * @since 1.16.2
         * @param string $css_class Default: `gv-grid gv-widgets-{zone}` where `{zone}` is replaced by the current `$zone` value. If the View has no results, adds ` gv-widgets-no-results`
         * @param string $zone Current widget zone, either `header` or `footer`
         * @param array $widgets Array of widget configurations for the current zone, as set by `gravityview_get_current_view_data()['widgets']`
         */
        $css_class = apply_filters('gravityview/widgets/wrapper_css_class', $default_css_class, $zone, $widgets);
        $css_class = gravityview_sanitize_html_class($css_class);
        // TODO Convert to partials
        ?>
		<div class="<?php 
        echo $css_class;
        ?>
">
			<?php 
        foreach ($rows as $row) {
            foreach ($row as $col => $areas) {
                $column = $col == '2-2' ? '1-2 gv-right' : $col . ' gv-left';
                ?>
					<div class="gv-grid-col-<?php 
                echo esc_attr($column);
                ?>
">
						<?php 
                if (!empty($areas)) {
                    foreach ($areas as $area) {
                        if (!empty($widgets[$zone . '_' . $area['areaid']])) {
                            foreach ($widgets[$zone . '_' . $area['areaid']] as $widget) {
                                do_action("gravityview_render_widget_{$widget['id']}", $widget);
                            }
                        }
                    }
                }
                ?>
					</div>
				<?php 
            }
            // $row
            ?>
			<?php 
        }
        // $rows
        ?>
		</div>

		<?php 
        /**
         * Prevent widgets from being called twice.
         * Checking for loop_start prevents themes and plugins that pre-process shortcodes from triggering the action before displaying. Like, ahem, the Divi theme and WordPress SEO plugin
         */
        if (did_action('loop_start')) {
            do_action($zone . '_' . $view_id . '_widgets');
        }
    }