diff --git a/functions.php b/functions.php index bf637b42b..ac3019373 100644 --- a/functions.php +++ b/functions.php @@ -163,6 +163,7 @@ function largo_customizer_css() { '/inc/jetpack.php', // Jetpack compatibility file '/inc/settings.php', // Additional site settings '/inc/taxonomy-archive-sidebars.php', // Custom sidebars for taxonomy archives + '/inc/update.php', // Largo Update stuff '/inc/widgets/author-bio.php', '/inc/widgets/site-blurb.php', ); diff --git a/inc/update.php b/inc/update.php new file mode 100644 index 000000000..934c82d5f --- /dev/null +++ b/inc/update.php @@ -0,0 +1,1200 @@ + 1.0 transitional block + of_set_option( 'largo_version', largo_version() ); + } + + // this is for post-1.0 stuff + if ( largo_need_updates_1() ) { + set_theme_mod( 'largo_version', largo_version() ); + } + + return true; +} + +/** + * Returns current version of largo as set in stylesheet. + * + * @since 0.3 + */ +function largo_version() { + $theme = wp_get_theme(); + $parent = $theme->parent(); + if ( ! empty( $parent ) ) { + return $parent->get( 'Version' ); + } + return $theme->get( 'Version' ); +} + +/** + * Wrapper for all granular "does largo need an update?" functions + * + * This wraps the update functions specific to various options frameworks + * + * @uses largo_need_updates_0 + * @uses largo_need_updates_1 + * @since 1.0 + * @return boolean Whether an update is needed + */ +function largo_need_updates() { + return largo_need_updates_0() || largo_need_updates_1(); +} + +/** + * Checks if updates need to be run for the pre-Largo-1.0 Options Framework + * + * This function used to be named largo_need_updates(); + * It was suffixed with a _0 for Largo 1.0 to largo_need_updates_0() to indicate that it + * applies to the 0.x branch of largo + * + * There's probably a better name for this. + * + * @since 0.3 + * + * @return boolean $result True if updates need to be run for the options framework stuff + */ +function largo_need_updates_0() { + // try to figure out which versions of the options are stored. Implemented in 0.3 + if ( of_get_option( 'largo_version' ) ) { + $compare = version_compare( largo_version(), of_get_option( 'largo_version' ) ); + + if ( $compare == 1 ) { + $return = true; + } else { + $return = false; + } + + // no need to update the options framework if the saved largo version is > 1.0, since + // that means that we've already run upgrades against the optionsframework after 1.0 + $post_optionsframework = version_compare( '1.0.0', of_get_option( 'largo_version' ), '<=' ); + if ( $post_optionsframework ) { + $return = false; + } + + return $return; + } + + // if 'largo_version' isn't present in the optionsframework, the settings are so old that we can safely ignore them for 1.0 and following + // or it's a fresh install, in which case we don't need to do anything regarding the optionsframework options + return false; +} + +/** + * Checks if updates need to be run for the theme_mods options introduced in Largo 1.0 + * + * @since 1.0 + * @return boolean $result True if updates need to be run. + */ +function largo_need_updates_1() { + if ( get_theme_mod( 'largo_version' ) ) { + $compare = version_compare( largo_version(), get_theme_mod( 'largo_version' ) ); + + if ( $compare == 1 ) { + return true; + } else { + return false; + } + } + + // if 'largo_version' isn't set in the theme mods, then this is a version of Largo that needs updates to be run + return true; +} + +/* -------------------------------------------------------- + * Upgrades for moving from 0.3 -> 0.4 + * + * In which many theme options became widgets + * ------------------------------------------------------ */ + +/** + * Puts new widgets into sidebars as appropriate based on old theme options + * + * @since 0.4 + */ +function largo_update_widgets() { + + /* checks and adds if necessary: + social_icons_display ('btm' or 'both') + show_tags + show_author_box + show_related_content + show_next_prev_nav_single + */ + $checks = array(); + + $checks['social_icons_display'] = array( + 'values' => array( 'btm', 'both' ), + 'widget' => 'largo-follow', + 'settings' => array( 'title' => '' ), + ); + + $checks['show_tags'] = array( + 'values' => array( '1' ), + 'widget' => 'largo-tag-list', + 'settings' => array( 'title' => __( 'Tags ', 'largo' ), 'tag_limit' => 20 ), + ); + + $checks['show_author_box'] = array( + 'values' => array( '1' ), + 'widget' => 'largo-author', + 'settings' => array( 'title' => __( 'Author', 'largo' ) ), + ); + + $checks['show_related_content'] = array( + 'values' => array( '1' ), + 'widget' => 'largo-related-posts', + 'settings' => array( 'title' => __( 'Read Next', 'largo' ), 'qty' => 1 ), + ); + + $checks['show_next_prev_nav_single'] = array( + 'values' => array( '1' ), + 'widget' => 'largo-prev-next-post-links', + 'settings' => array(), + ); + + //loop thru, see if value is present, then see if widget exists, if not, create one + $previous_options = largo_retrieve_previous_options(); + foreach( $checks as $option => $i ) { + $opt = $previous_options[$option]; + if ( $i['values'] === NULL || in_array( $opt, $i['values'] ) ) { + //we found an option that suggests we need to add a widget. + //if there's not aleady one present, add it + if ( ! largo_widget_in_region( $i['widget'] ) ) { + largo_instantiate_widget( $i['widget'], $i['settings'], 'article-bottom' ); + } + } + } +} + +/** + * @since 0.4 + */ +function largo_transition_nav_menus() { + $locations = get_nav_menu_locations(); + $main_nav = wp_get_nav_menu_object( 'Main Navigation' ); + if ( ! $main_nav ) { + $main_nav_id = wp_create_nav_menu( 'Main Navigation' ); + $locations['main-nav'] = $main_nav_id; + } else { + $locations['main-nav'] = $main_nav->term_id; + } + + // Get the menu items for each menu + $existing_items = array(); + foreach ( $locations as $location => $id ) { + $existing_items[$location] = wp_get_nav_menu_items( $id ); + } + + // These nav menu locations/menus get folded into main-nav menu + $transition = array( 'navbar-categories', 'navbar-supplemental', 'sticky-nav' ); + + // Move all the category, supplemental items to main-nav. + // Remove category and supplemental navs. + foreach ( $transition as $location_slug ) { + if ( isset( $existing_items[$location_slug] ) ) { + $items = $existing_items[$location_slug]; + if ( $items ) { + foreach ( $items as $idx => $item ) { + $meta = get_metadata( 'post', $item->ID ); + $attrs = array( + 'menu-item-type' => $meta['_menu_item_type'][0], + 'menu-item-menu-item-parent' => $meta['_menu_item_menu_item_parent'][0], + 'menu-item-parent-id' => $meta['_menu_item_menu_item_parent'][0], + 'menu-item-object-id' => $meta['_menu_item_object_id'][0], + 'menu-item-object' => $meta['_menu_item_object'][0], + 'menu-item-target' => $meta['_menu_item_target'][0], + 'menu-item-classes' => $meta['_menu_item_classes'][0], + 'menu-item-xfn' => $meta['_menu_item_xfn'][0], + 'menu-item-url' => $meta['_menu_item_url'][0], + 'menu-item-title' => $item->post_title, + 'menu-item-attr-title' => $item->post_excerpt + ); + wp_update_nav_menu_item( $locations['main-nav'], $item->ID, $attrs ); + } + } + // Get rid of the menu + wp_delete_nav_menu( $locations[$location_slug] ); + unset( $locations[$location_slug] ); + } + } + set_theme_mod( 'nav_menu_locations', $locations ); +} + +/** + * Updates post prominence term descriptions iff they use the old language + * + * This function can be added to the `init` action to force an update of prominence term descriptions: + * add_action('init', 'largo_update_prominence_term_descriptions'); + * + * This function does not touch custom prominence term descriptions, except those that are identical to the descriptions of current or 0.3 prominence term descriptions. + * + * @since 0.4 + * + * @uses largo_update_prominence_term_description_single + */ +function largo_update_prominence_term_descriptions() { + // see https://github.com/INN/Largo/issues/210 + + $terms = get_terms( 'prominence', array( + 'hide_empty' => false, + 'fields' => 'all' + ) ); + + // prevent PHP warnings in case no terms returned + if ( gettype( $terms ) != "array" ) { + return false; + } + + $term_descriptions = array_map( function( $arg ) { return $arg->description; }, $terms); + + $largoOldProminenceTerms = array( + array( + 'name' => __( 'Sidebar Featured Widget', 'largo' ), + 'description' => __( 'If you are using the Featured Posts widget in a sidebar, add this label to posts to determine which to display in the widget.', 'largo' ), + 'olddescription' => __( 'If you are using the Sidebar Featured Posts widget, add this label to posts to determine which to display in the widget.', 'largo' ), + 'slug' => 'sidebar-featured' + ), + array( + 'name' => __( 'Footer Featured Widget', 'largo' ), + 'description' => __( 'If you are using the Featured Posts widget in the footer, add this label to posts to determine which to display in the widget.', 'largo' ), + 'olddescription' => __( 'If you are using the Footer Featured Posts widget, add this label to posts to determine which to display in the widget.', 'largo' ), + 'slug' => 'footer-featured' + ), + array( + 'name' => __( 'Featured in Category', 'largo' ), + 'description' => __( 'This will allow you to designate a story to appear more prominently on category archive pages.', 'largo' ), + 'olddescription' => __( 'Not yet implemented, in the future this will allow you to designate a story (or stories) to appear more prominently on category archive pages.', 'largo' ), + 'slug' => 'category-featured' + ), + array( + 'name' => __( 'Homepage Featured', 'largo' ), + 'description' => __( 'Add this label to posts to display them in the featured area on the homepage.', 'largo' ), + 'olddescription' => __( 'If you are using the Newspaper or Carousel optional homepage layout, add this label to posts to display them in the featured area on the homepage.', 'largo' ), + 'slug' => 'homepage-featured' + ), + array( + 'name' => __('Homepage Top Story', 'largo'), + 'description' => __( 'If you are using a "Big story" homepage layout, add this label to a post to make it the top story on the homepage', 'largo' ), + 'olddescription' => __( 'If you are using the Newspaper or Carousel optional homepage layout, add this label to label to a post to make it the top story on the homepage.', 'largo' ), + 'slug' => 'top-story' + ), + ); + + foreach ( $largoOldProminenceTerms as $update ) { + largo_update_prominence_term_description_single( $update, $term_descriptions ); + } +} + +/** + * Compares an array containing an old and new prominence term description and the appropriate slug and name to an array of current term descriptions. For each term whose current description matches the old description, the function updates the current description to the new description. + * + * This function contains commented-out logic that will allow you to from description to olddescription + * + * @since 0.4 + * + * @param array $update The new details for the prominence tax term to be updated + * @param array $term_descriptions Array of prominence terms, each prominence term as an associative array with keys: name, description, olddescription, slug + * @uses wp_update_term + * @uses clean_term_cache + * + */ +function largo_update_prominence_term_description_single( $update, $term_descriptions ) { + $logarray = array(); + + // Toggle comment on these two lines to revert to old descriptions. + if ( in_array( $update['olddescription'], $term_descriptions ) ) { +# if ( in_array( $update['description'], $term_descriptions ) ) { + $id = get_term_by( 'slug', $update['slug'], 'prominence', 'ARRAY_A' ); + // Comment out this function call to avoid all prominence term updates. +# /* + wp_update_term( + $id['term_id'], 'prominence', + array( + 'name' => $update['name'], + // Toggle comment on these two lines to revert to old descriptions. + 'description' => $update['description'], +# 'description' => $update['olddescription'], + 'slug' => $update['slug'] + ) + ); +# */ + $logarray[] = 'Updated description of "' . $update['name'] . '" from "'. $update['olddescription'] . '" to "' . $update['description'] . '"'; + // Clean the entire prominence term cache + clean_term_cache( $id['term_id'], 'prominence', true ); + } + + return $update; +} + +/** + * Update miscellaneous settings + * + * @since 0.4 + */ +function largo_force_settings_update() { + $options = array(); + // paste in default settings from options.php after this line; + $options[] = array( + 'desc' => __( 'Starting with version 0.4, Largo introduced a new single-post template that more prominently highlights article content, which is the default. For backward compatibility, the pre-0.3 version is also available.', 'largo' ), + 'id' => 'single_template', + 'std' => 'normal', + 'type' => 'select', + 'options' => array( + 'normal' => 'One Column (Standard Layout)', + 'classic' => 'Two Column (Classic Layout)' + ) + ); + + foreach ( $options as $option ) { + of_set_option( $option['id'], $option['std'] ); + } +} + +/* -------------------------------------------------------- + * Upgrades for moving from 0.4 -> 0.5 + * + * In which top stories are now registered by default. + * ------------------------------------------------------ */ + +/** + * Remove "top-story" prominence term to avoid conflicts with homepages that will register it + * + * @return array $results Deleted prominence terms + */ +function largo_remove_topstory_prominence_term() { + $terms = get_terms( + 'prominence', + array( + 'hide_empty' => false, + 'fields' => 'all' + ) + ); + + $del_terms = array(); + foreach ( $terms as $term ) { + $term = (array)$term; // $term is originally a stdClass::__set_state(array( ... )) + + // get old "Top Story", which uses the same slug 'top-story' as the new "Homepage Top Story" + if ( $term['name'] == 'Top Story' ) { + wp_update_term( $term['term_taxonomy_id'], 'prominence', array( + 'name' => __( 'Homepage Top Story', 'largo' ), + 'description' => __( 'If you are using a "Big story" homepage layout, add this label to a post to make it the top story on the homepage', 'largo' ), + 'slug' => 'top-story', + 'parent' => null + )); + } else if ( preg_match('/top-story-/', $term['slug'], $matches) ) { + // get 'top-story-N' but not new 'top-story' + wp_delete_term( $term['term_taxonomy_id'], 'prominence' ); + $del_terms[] = $term; + } + } + return $del_terms; +} + + +/* -------------------------------------------------------- + * Upgrades for moving from 0.5 -> 1.0 + * + * In which we transition away from the options framework + * to WordPress' theme_mods + * ------------------------------------------------------ */ + +/** + * Copy selected settings from the options framework to the theme_mods settings + * + * @since 1.0 + * @link https://codex.wordpress.org/Theme_Modification_API + * @return boolean Whether the function was successfully completed + */ +function largo_optionsframework_to_theme_mods() { + $updates = array( + /* + * Example array, with optional callback + array( + 'from' => 'site_blurb', + 'to' => 'site_blurb', + 'callback' => something that's callable, be that an anonymous function or a string function name + ), + */ + array( + 'from' => 'site_blurb', + 'to' => 'site_blurb', + ), + ); + + // cache this to prevent fewer db reads + $config = get_option( 'optionsframework' ); + $optionsframework = get_option( $config['id'] ); + + // strange + if ( ! is_array( $optionsframework ) ) { + return false; + } + + foreach ( $updates as $setting ) { + // abort + if ( ! is_string( $setting['from'] ) || ! is_string( $setting['to'] ) ) { + break; + } + + $value = $optionsframework[ $setting['from'] ]; + + // callback allowing modification of value + if ( isset( $setting['callback'] ) ) { + if ( is_callable( $setting['callback'] ) ) { + $value = call_user_func( $setting['callback'], $value ); + } + } + error_log(var_export( $value, true)); + + set_theme_mod( $setting['to'], $value ); + } + + return true; +} + +/* -------------------------------------------------------- + * Always run + * + * Functions that should run any time the largo version + * number bumps. + * ------------------------------------------------------ */ + +/** + * Replace deprecated widgets with new widgets + * + * To add widgets to this list of widgets to be upgraded: + * - Add the deprecated widget class and its replacement to $upgrades + * + * @uses largo_get_widget_basename + * @uses largo_get_widget_number + * @since 0.5.3 + */ +function largo_replace_deprecated_widgets() { + + // This defines the classes of widget that will be updated, the class that they will be + // replaced with, and the default args on the replacement widget that must be set. + $upgrades = array( + /* + * Get the class name from the 'classname' variable in the $widget_ops array used to parent::__construct() the widget: + * https://github.com/INN/Largo/blob/master/inc/widgets/largo-recent-posts.php#L14 + * + * The callback function, if it is set (and it does not have to be), will receive two arguments. + * The first one is the deprecated widget's $instance options. + * The second argument is the replacement widget's $instance options. + * Your callback function should return the replacement widget's $instance options. + * + * Example widget description + 'old-widget-class' => array( + 'class' => 'new-widget-class', + 'callback' => 'name_of_callback_function', + 'defaults' => array( + // The $instance arguments for the widget go here. + 'ignore_global_shown_ids' => true, + 'show_thumbnails' => '1', + 'display_chicken' => false + ) + */ + 'largo-footer-featured' => array( + 'class' => 'largo-recent-posts', + 'defaults' => array( + 'taxonomy' => 'prominence', + 'term' => 'footer-featured', + 'title' => __( 'In Case You Missed It', 'largo' ) + ) + ), + 'largo-sidebar-featured' => array( + 'class' => 'largo-recent-posts', + 'defaults' => array( + 'taxonomy' => 'prominence', + 'term' => 'sidebar-featured', + 'title' => __( 'We Recommend', 'largo' ) + ) + ), + 'largo-featured' => array( + 'class' => 'largo-recent-posts', + 'callback' => 'largo_deprecated_callback_largo_featured', + 'defaults' => array( + 'taxonomy' => 'prominence', + 'title' => __( 'Largo Featured Posts', 'largo' ) + ) + ) + ); + $all_widgets = get_option( 'sidebars_widgets' ); + + /* + * Find the widgets that need to be replaced, + * Move their arguments to new widgets, + * Name the new widgets appropriately + * Place the new widgets into the widgets list and into the sidebars list + */ + foreach ( $all_widgets as $region => $current_sidebar ) { + // Unlike largo_check_deprecated_widgets, this does not care if the + // widget is inactive. This replaces *all* widgets. + if ( $region != 'array_version' && is_array($current_sidebar) ) { + foreach ( $current_sidebar as $current_widget_slug ) { + foreach ( $upgrades as $old_widget_name => $upgrade ) { + + // Check if the current widget matches a widget in + // $updates that needs to be replaced. + if ( strpos( $current_widget_slug, $old_widget_name ) === 0 ) { + + // Update all this here and now, in case the indexes are off because this + // has been meddled with in a previous loop. + $local_all_widgets = get_option( 'sidebars_widgets' ); + $local_current_sidebar = $local_all_widgets[$region]; + $index = array_search( $current_widget_slug, $local_current_sidebar ); + + /* + * So many variables ... + * + * $local_all_widgets: Associative array of $region a sidebar or widget area => $local_current_sidebar array of widget slugs in region + * $local_current_sidebar: Array of the widgets in the current sidebar/widget area/$region + * $current_widget_slug: the old widget's ID: slug-widget-2 + * $old_widget_name: The slug of the widget that needs to be updated, from $upgrades: slug + * $region: the id of the current sidebar/widget area + * $index: Where $current_widget_slug is located in $local_current_sidebar + * $basename: the slug of the widget $current_widget_slug, when you remove the prefix widget_ and postfix -number + * $all_instances_of_widget: All instance of $current_widget_slug in all sidebars. + * $upgrade['class'] : The class of the replacement widget, which needs -widget appended to it. + * $upgrade['callback'] : name of the callback function that should be run + * $upgrade['defaults'] : Default instance arguments for the replacement widget. + * $all_instances_of_upgrade: All instances of $$upgrade['class'] in all sidebars. + * $upgrade_instance_args: The merged old args of the widget with the args from $upgrade['defaults'] + * $liw_return: array returned by largo_instantiate_widget with the widget's slug info and place in the sidebar. + */ + + // Let's steal some logic from INN/wp-scripts/inc/class-cmd-sidebars.php's dump() + $basename = largo_get_widget_basename( $current_widget_slug ); + $number = largo_get_widget_number( $current_widget_slug ); + if ( ! empty( $basename ) ) { + // get all the widgets of this basename + $all_instances_of_widget = get_option( 'widget_' . $basename, false ); + + $upgrade_instance_args = array_replace( $upgrade['defaults'], $all_instances_of_widget[$number] ); + + /** + * Call a callback specified in the widget upgrade options in largo_replace_deprecated_widgets() + * + * @param array The deprecated widget's $instance variables + * @param array The replacement widget's default $instance variables + * @return array The replacement widget's $instance variables + */ + if ( isset( $upgrade['callback'] ) ) { + $upgrade_instance_args = call_user_func( $upgrade['callback'], $all_instances_of_widget[$number], $upgrade_instance_args ) ; + } + + // create the new widget. + $liw_return = largo_instantiate_widget( $upgrade['class'], $upgrade_instance_args, $region ); + + // remove the old widget + unset( $all_instances_of_widget[$number] ); + update_option( 'widget_' . $basename, $all_instances_of_widget ); + // @todo: if there are no widgets left in the array, why not just remove the option? + + // update $local_current_sidebar + $local_all_widgets = get_option( 'sidebars_widgets' ); + $local_current_sidebar = $local_all_widgets[$region]; + + // Shuffle the new widget around + // replace the old widget slug with the new widget slug + $local_current_sidebar[$index] = $liw_return['id']; + + // remove the now-duplicate instance of the old widget + // added by largo_instantiate_widget + unset( $local_current_sidebar[$liw_return['place']] ); + $local_all_widgets[$region] = $local_current_sidebar; + update_option( 'sidebars_widgets', $local_all_widgets ); + } + } + } + } + } + } +} + +/** + * Callback for updating the Largo Featured widget in largo_replace_deprecated_widgets() + * + * @since 0.5.3 + * @see largo_replace_deprecated_widgets + * @param array $deprecated the deprecated widget's $instance variables + * @param array $replacement the replacement widget's $instance variables + * @return array $result the replacement widget's $instance variables + */ +function largo_deprecated_callback_largo_featured( $deprecated, $replacement ) { + if ( isset( $deprecated['thumb'] ) ) { + $replacement['thumbnail_display'] = $deprecated['thumb']; + } + return $replacement; +} +/* -------------------------------------------------------- + * Update helper functions + * ------------------------------------------------------ */ + +/** + * Checks to see if a given widget is in a given region already + * + * @since 0.5.2 + * @return boolean $result Whether or not the widget was found. + */ +function largo_widget_in_region( $widget_name, $region = 'article-bottom' ) { + + $widgets = get_option( 'sidebars_widgets '); + + if ( !isset( $widgets[$region] ) ) + return false; + + foreach( $widgets[$region] as $key => $widget ) { + if ( stripos( $widget, $widget_name ) === 0 ) { + return true; //we found a copy of this widget! Note this may return a false positive if the widget we're checking is the same name (but shorter) as another kind of widget + } + } + return false; // the widget wasn't there +} + +/** + * Inserts a widget programmatically. + * This is slightly dangerous as it makes some assumptions about existing plugins + * if $instance_settings are wrong, bad things might happen + * + * @since 0.5 + * + * @param string $kind. Kind of widget to instantiate. + * @param array $instance_settings. Settings for that array. + * @param string $region. Sidebar region to add to. + * @return array $result array('id' => the id with number of the new widget , 'place' => the index of the id in its region ) + */ +function largo_instantiate_widget( $kind, $instance_settings, $region ) { + + $defaults = array( + 'widget_class' => 'default', + 'hidden_desktop' => 0, + 'hidden_tablet' => 0, + 'hidden_phone' => 0, + 'title_link' => '' + ); + + $instance_id = 2; // default, not sure why it always seems to start at 2 + $full_kind = 'widget_' . str_replace("_", "-", $kind) . '-widget'; + + // step 1: add the widget instance to the database and get the ID + $widget_instances = get_option( $full_kind ); + + // no instances of this exist, yay + if ( ! $widget_instances ) { + update_option( $full_kind, + array( + 2 => wp_parse_args( $instance_settings, $defaults ), + '_multiwidget' => 1, + ) + ); + + } else { + + //figure out what ID we're creating. Don't just use count() as things might get deleted or something... + //@TODO there's probably a smarter way to do this... + while ( array_key_exists( $instance_id, $widget_instances) ) { + $instance_id++; + } + + //pop off _multiwidget, add our element to the end, then add _multiwidget back + $new_instances = array_pop( $widget_instances ); + $widget_instances[ $instance_id ] = wp_parse_args( $instance_settings, $defaults ); + $widget_instances[ '_multiwidget' ] = 1; + update_option( $full_kind, $widget_instances ); + } + + // step 2: add the widget instance we just created to the region; this isn't so bad + $region_widgets = get_option( 'sidebars_widgets' ); + $region_widgets[ $region ][] = $kind . '-widget-' . $instance_id; + update_option( 'sidebars_widgets', $region_widgets ); + $place = array_search($kind . '-widget-' . $instance_id, $region_widgets[$region]); + + return array( + 'id' => $kind . '-widget-' . $instance_id, + 'place' => $place + ); + +} + +/** + * Utility function to get the basename of a widget from the widget's slug + * + * @since 0.5.3 + */ +function largo_get_widget_basename( $slug ) { + if ( preg_match( '/^(.*)\-\d+$/', $slug, $matches ) ) { + return $matches[1]; + } + return false; +} +/** + * Utility function to get the number of a widget from the widget's slug + * + * @since 0.5.3 + */ +function largo_get_widget_number( $slug ) { + if ( preg_match( '/^.*\-(\d+)$/', $slug, $matches ) ) { + return $matches[1]; + } + return false; +} + +/* -------------------------------------------------------- + * Update.php admin page logic. + * ------------------------------------------------------ */ + +/** + * Add an admin notice if largo needs to be updated. + * + * @since 0.3 + */ +function largo_update_admin_notice() { + if ( ! current_user_can( 'update_themes' ) ) { + return; + } + if ( largo_need_updates() && ! ( isset( $_GET['page'] ) && $_GET['page'] == 'update-largo' ) ) { +?> +

+ visit the update page to apply a required database update.', 'largo' ), + admin_url( 'index.php?page=update-largo' ) + ); + ?> +

+ + +
+
+

Largo Database Update

+
+

+ +

: +

+

+

+

+

+ +

+ + +

+ + +

+

+
+ __( 'An error occurred.', 'largo' ), + 'success' => false + )); + wp_die(); + } + + if ( ! largo_need_updates() ) { + print json_encode(array( + 'status' => __( 'Finished. No update was required.', 'largo' ), + 'success' => false + )); + wp_die(); + } + + $ret = largo_perform_update(); + if ( ! empty( $ret ) ) { + if ( version_compare( of_get_option( 'largo_version' ), '0.4' ) < 0 ) { + $message = __( "Thank you -- the update is complete. Don\'t forget to check your site settings!", "largo" ); + } else { + $message = __( 'Thank you -- the update is complete.', 'largo' ); + } + print json_encode( array( + 'status' => $message, + 'success' => true + ) ); + wp_die(); + } else { + print json_encode( array( + 'status' => __( 'There was a problem applying the update. Please try again.', 'largo' ), + 'success' => false + )); + wp_die(); + } +} +add_action( 'wp_ajax_largo_ajax_update_database', 'largo_ajax_update_database' ); + +/** + * A singleton utility class for preserving and retrieving previous Largo options + * + * @since 0.5.3 + */ +class LargoPreviousOptions { + + protected static $version; + + protected static function _setVersion( $version ) { + self::$version = $version; + } + + protected static function _getSuffix() { + $version = ( ! empty( self::$version ) ) ? self::$version : of_get_option( 'largo_version', '' ); + if ( !empty( $version ) ) { + self::_setVersion( $version ); + return '_' . self::$version; + } else { + return '_pre_0.4'; + } + } + + /** + * Call this method before saving theme options for the first time after updating Largo + * to preserve the state of theme options for the previous version. + */ + public static function preserve() { + $config = get_option( 'optionsframework' ); + + if ( isset( $config['id'] ) ) { + $options = get_option( $config['id'] ); + + if ( ! empty( $options['largo_version'] ) ) { + self::_setVersion($options['largo_version']); + } else { + self::_setVersion('pre_0.4'); + } + update_option( $config['id'] . self::_getSuffix(), $options ); + return $options; + } + + return array(); + } + + /** + * Retrieve the theme options for the version of Largo that immediately preceeded the + * currently-deployed version. + * + * Optionally, retrieve a previous set of theme options by passing a version string to the + * method. + * + * @param string $largo_version for example '0.5.2' + */ + public static function retrieve( $largo_version = false ) { + $config = get_option( 'optionsframework' ); + + if ( isset( $config['id'] ) ) { + $options = get_option( $config['id'] ); + + if ( ! empty( $largo_version ) ) { + self::_setVersion( $largo_version ); + } + + return get_option( $config['id'] . self::_getSuffix() ); + } + + return array(); + } + +} + +/** + * Convenience function for storing the theme options for the version of the theme that immediately + * preceeded the currently-deployed version. + * + * @since 0.5.3 + */ +function largo_preserve_previous_options() { + return LargoPreviousOptions::preserve(); +} + +/** + * Convenience function for retrieving the theme options for the version of the theme that immediately + * preceeded the currently-deployed version. + * + * @since 0.5.3 + */ +function largo_retrieve_previous_options( $largo_version = false ) { + return LargoPreviousOptions::retrieve( $largo_version ); +} + +/** + * If Largo needs to be updated, prevent the user from access the Theme Options edit page. + * + * @since 0.5.3 + */ +function largo_block_theme_options_for_update() { + if ( largo_need_updates() ) { + add_action( 'admin_init', function() { + remove_submenu_page( 'themes.php', 'options-framework' ); + }); + + add_theme_page( + __( 'Theme Options', 'options_framework_theme' ), + __( 'Theme Options', 'options_framework_theme' ), + 'update_themes', + 'largo-block-theme-options', + 'largo_block_theme_options' + ); + } +} +add_action( 'admin_menu', 'largo_block_theme_options_for_update', 10 ); + +/** + * Displays a message indicating the user should update their Largo install before + * attempting to edit Theme Options + * + * @since 0.5.3 + */ +function largo_block_theme_options() { ?> +

+ visit the update page to apply required Largo updates before editing Theme Options.', 'largo' ), + admin_url( 'index.php?page=update-largo' ) + ); ?> +

+' + data.status + ''); + else + parent.html('
' + data.status + '
'); + + $('[href="themes.php?page=largo-block-theme-options"]').attr('href', 'themes.php?page=options-framework'); + }, + error: function() { + throw "There was an error running the update."; + } + }) + return false; + }); +})();