       1: <?php
       2: /**
       3:  *
       4:  */
       5: class Sidecar_Plugin_Base extends Sidecar_Singleton_Base {
       6:   /**
       7:    * @var string
       8:    */
       9:   var $plugin_class;
      10:
      11:   /**
      12:    * @var string
      13:    */
      14:   var $plugin_class_base;
      15:
      16:   /**
      17:    * @var string
      18:    */
      19:   var $plugin_name;
      20:
      21:   /**
      22:    * Dashed version of $this->plugin_name
      23:    *
      24:    * @var string
      25:    */
      26:   var $plugin_slug;
      27:
      28:   /**
      29:    * ID of plugin used by WordPress in get_option('active_plugins')
      30:    *
      31:    * @var string
      32:    */
      33:   var $plugin_id;
      34:
      35:   /**
      36:    * @var string
      37:    */
      38:   var $plugin_version;
      39:
      40:   /**
      41:    * @var string
      42:    */
      43:   var $plugin_title;
      44:
      45:   /**
      46:    * @var string
      47:    */
      48:   var $plugin_label;
      49:
      50:   /**
      51:    * @var string
      52:    */
      53:   var $css_base;
      54:
      55:   /**
      56:    * @var string
      57:    */
      58:   var $plugin_file;
      59:
      60:   /**
      61:    * @var string
      62:    */
      63:   var $plugin_path;
      64:
      65:   /**
      66:    * @var string Minimum PHP version, defaults to min version for WordPress
      67:    */
      68:   var $min_php = '5.2.4';
      69:
      70:   /**
      71:    * @var string Minimum WordPress version, defaults to first version requiring PHP 5.2.4.
      72:    */
      73:   var $min_wp = '3.2';
      74: //  /**
      75: //   * @var string Cron recurrance
      76: //   */
      77: //  var $cron_recurrance = 'hourly';
      78: //  /**
      79: //   * @var string Key used for cron for this plugin
      80: //   */
      81: //  var $cron_key;
      82:
      83:   /**
      84:    * @var array Array of URLs defined for handle use by plugin.
      85:    */
      86:   protected $_urls = array();
      87:
      88:   /**
      89:    * @var array Array of Image file names defined for handle use by plugin.
      90:    */
      91:   protected $_images = array();
      92:
      93:   /**
      94:    * @var bool|array
      95:    */
      96:   protected $_shortcodes = false;
      97:
      98:   /**
      99:    * @var bool|array
     100:    */
     101:   protected $_forms = false;
     102:
     103:   /**
     104:    * @var bool|array
     105:    */
     106:   protected $_admin_pages = array();
     107:
     108:   /**
     109:    * @var array Array of Meta Links for the WordPress plugin page.
     110:    */
     111:   protected $_meta_links = array();
     112:
     113:   /**
     114:    * @var bool|array|RESTian_Client
     115:    */
     116:   protected $_api = false;
     117:
     118:   /**
     119:    * @var Sidecar_Admin_Page
     120:    */
     121:   protected $_current_admin_page;
     122:
     123:   /**
     124:    * @var Sidecar_Form
     125:    */
     126:   protected $_current_form;
     127:
     128:   /**
     129:    * @var bool
     130:    */
     131:   protected $_initialized = false;
     132:
     133:   /**
     134:    * @var Sidecar_Plugin_Settings
     135:    */
     136:   protected $_plugin_settings;
     137:
     138:   /**
     139:    * @var
     140:    */
     141:   protected $_default_settings_values;
     142:
     143:   /**
     144:    * @var string
     145:    */
     146:   var $option_name;
     147:
     148:   /**
     149:    * @var string
     150:    */
     151:   var $needs_ajax = false;
     152:
     153:   /**
     154:    * @var bool
     155:    */
     156:   var $needs_settings = true;
     157:
     158:   /**
     159:    * @var bool
     160:    */
     161:   protected $_admin_initialized = false;
     162:
     163:
     164:
     165:
     166:   /**
     167:    * @param $class_name
     168:    * @param $filepath
     169:    */
     170:   function set_api_loader( $class_name, $filepath ) {
     171:     $this->_api = array(
     172:       'class_name' => $class_name,
     173:       'filepath' => $filepath,
     174:     );
     175:   }
     176:
     177:   /**
     178:    * @return bool
     179:    */
     180:   function has_api() {
     181:     return false !== $this->get_api();
     182:   }
     183:
     184:   /**
     185:    * Returns a RESTian_Client object if set_api_loader() has previously been called with correct info, false otherwise.
     186:    * @return bool|RESTian_Client
     187:    */
     188:   function get_api() {
     189:     if ( ! $this->_api instanceof RESTian_Client ) {
     190:       /**
     191:        * @todo fix this to work on Windows
     192:        */
     193:       $filepath= '/' == $this->_api['filepath'][0] ? $this->_api['filepath'] : "{$this->plugin_path}/{$this->_api['filepath']}";
     194:
     195:       if ( is_file( $filepath) ) {
     196:         require_once( $filepath );
     197:         if ( class_exists( $this->_api['class_name'] ) ) {
     198:           $class_name = $this->_api['class_name'];
     199:           // @var RESTian_Client $this->_api
     200:           $this->_api = new $class_name( $this );
     201:         }
     202:         /**
     203:          * @todo Verify we still need this
     204:          */
     205:         $this->_api->initialize_client();
     206:       }
     207:     }
     208:     return $this->_api;
     209:   }
     210:
     211:   /**
     212:    * @param RESTian_Client $api
     213:    */
     214:   function set_api( $api ) {
     215:     if ( empty( $this->_forms ) ) {
     216:       /**
     217:        * What about plugins with an API but no need for forms?  Is that possible since forms=settings in Sidecar?
     218:        */
     219:       $error_message = __( '$plugin->set_api($api) cannot be called before forms have been added. Please call $plugin->add_form() in $plugin->initialize_plugin() at least once prior to calling set_api().', 'sidecar' );
     220:       trigger_error( $error_message );
     221:       exit;
     222:     }
     223:     $api->caller = $this;
     224:     $api->initialize_client();
     225:     $this->_api = $api;
     226:     if ( $this->_admin_initialized ) {
     227:       /**
     228:        * If Admin has been initialized then set grant.
     229:        * If not, wait to set until after initialization
     230:        * otherwise we get into a bad spiral of not-defined-yet.
     231:        */
     232:       $this->_api->set_grant( $this->get_grant() );
     233:     }
     234:   }
     235:
     236:   function is_saving_widget() {
     237:     global $pagenow;
     238:     return isset( $_POST['action'] ) && isset( $_POST['id_base'] )
     239:       && 'admin-ajax.php' == $pagenow && 'save-widget' == $_POST['action'];
     240:   }
     241:   /**
     242:    * @param array $args
     243:    */
     244:   function on_load( $args = array() ) {
     245:
     246:     /**
     247:      * If we are saving a widget then of course we need ajax,
     248:      * and I'm pretty sure we'll need to add a lot of checks here
     249:      * as new AJAX use-cases are discovered.
     250:      */
     251:     if ( ! $this->needs_ajax && $this->is_saving_widget() ) {
     252:       $this->needs_ajax = true;
     253:     }
     254:     /*
     255:      * If running an AJAX callback and either $this->needs_ajax or $args['needs_ajax'] is false then bypass the plugin.
     256:      *
     257:      * To enable AJAX support in a plugin either:
     258:      *
     259:      *  1. Create a __construct in plugin class, set $this->needs_ajax=true then call parent::_construct(), or
     260:      *
     261:      *  2. Pass array( 'needs_ajax' => true ) to plugin's required constructor at end of plugin file,
     262:      *     i.e. new MyPlugin( array( 'needs_ajax' => true ) );
     263:      *
     264:      */
     265:     if ( defined( 'DOING_AJAX' ) && DOING_AJAX && ( ! $this->needs_ajax || ( isset( $args['needs_ajax'] ) && ! $args['needs_ajax'] ) ) )
     266:       return;
     267:
     268:     $this->plugin_class = get_class( $this );
     269:
     270:     /**
     271:      * Copy properties in from $args, if they exist.
     272:      */
     273:     foreach( $args as $property => $value )
     274:       if ( property_exists(  $this, $property ) )
     275:         $this->$property = $value;
     276:
     277:     add_action( 'init', array( $this, '_init' ) );
     278:     add_action( 'wp_loaded', array( $this, '_wp_loaded' ) );
     279:     add_action( 'wp_print_styles', array( $this, '_wp_print_styles' ) );
     280:     add_action( 'save_post', array( $this, '_save_post' ) );
     281:
     282:     $this->plugin_class_base = preg_replace( '#^(.*?)_Plugin$#', '$1', $this->plugin_class );
     283:
     284:     if ( ! $this->plugin_name )
     285:       $this->plugin_name = strtolower( $this->plugin_class_base );
     286:
     287:     if ( ! $this->option_name )
     288:       $this->option_name = "{$this->plugin_name}_settings";
     289:
     290: //    if ( ! $this->cron_key )
     291: //      $this->cron_key = "{$this->plugin_name}_cron";
     292:
     293:     if ( $this->is_plugin_page_action() ) {
     294:       global $plugin;
     295:       if ( ! isset( $plugin ) ) {
     296:         /*
     297:          * This plugin is being activated
     298:          */
     299:         $this->plugin_id = filter_input( INPUT_GET, 'plugin', FILTER_SANITIZE_STRING );
     300:         $this->plugin_file = WP_PLUGIN_DIR . '/' . $this->plugin_id;
     301:       } else if ( file_exists( $plugin ) ) {
     302:         /**
     303:          * This is evidently the case during activation using Imperative
     304:          */
     305:         $this->plugin_file = $plugin;
     306:         $this->plugin_id = basename( dirname( $plugin ) ) . '/' . basename( $plugin );
     307:       } else {
     308:         /*
     309:          * Another plugin is being activated
     310:          */
     311:         $this->plugin_file = WP_PLUGIN_DIR . '/' . $plugin;
     312:         $this->plugin_id = $plugin;
     313:       }
     314:       add_action( 'plugins_loaded', array( $this, '_plugins_loaded' ) );
     315:       add_action( "activate_{$this->plugin_id}", array( $this, '_activate_plugin' ), 0 );
     316:       register_activation_hook( $this->plugin_id, array( $this, '_activate' ) );
     317:     } else if ( ! WP_Library_Manager::$loading_plugin_loaders && $this->is_verified_plugin_deletion() ) {
     318:       if ( preg_match( '#^uninstall_(.*?)$#', current_filter(), $match ) ) {
     319:         $this->plugin_file = WP_PLUGIN_DIR . "/{$match[1]}";
     320:       } else {
     321:         /*
     322:          * @todo My god this is a hack! I really need help from WordPress core here.
     323:          * @todo Blame: https://core.trac.wordpress.org/ticket/22802#comment:41
     324:          */
     325:         $backtrace = debug_backtrace();
     326:         foreach( $backtrace as $index => $call ) {
     327:           if ( preg_match( '#/wp-admin/includes/plugin.php$#', $call['file'] ) ) {
     328:             $this->plugin_file = $backtrace[$index-1]['file'];
     329:             break;
     330:           }
     331:         }
     332:       }
     333:       $this->plugin_id = basename( dirname( $this->plugin_file ) ) . '/' . basename( $this->plugin_file );
     334:     } else if ( false !== WP_Library_Manager::$uninstalling_plugin && preg_match( '#' . preg_quote( WP_Library_Manager::$uninstalling_plugin ) . '$#', $GLOBALS['plugin'] ) ) {
     335:       /**
     336:        * We are uninstalling a plugin, and the plugin we are uninstalling matches the global $plugin value
     337:        * which means we ar loading the plugin we want to uninstall (vs. loading a different plugin on `same page load.)
     338:        */
     339:       global $plugin;
     340:       $this->plugin_file = $plugin;
     341:       $this->plugin_id = WP_Library_Manager::$uninstalling_plugin;
     342:     } else {
     343:       /**
     344:        * Grab the plugin file name from one the global values set when the plugin is included.
     345:        * @see: https://wordpress.stackexchange.com/questions/15202/plugins-in-symlinked-directories
     346:        * @see: https://wordpress.stackexchange.com/a/15204/89
     347:        */
     348:       global $mu_plugin, $network_plugin, $plugin;
     349:       if ( isset( $mu_plugin ) ) {
     350:         $this->plugin_file = $mu_plugin;
     351:       } else if ( isset( $network_plugin ) ) {
     352:         $this->plugin_file = $network_plugin;
     353:       } else if ( isset( $plugin ) ) {
     354:         $this->plugin_file = $plugin;
     355:       } else {
     356:         trigger_error( sprintf( __( 'Plugin %s only works when loaded by WordPress.' ), $this->plugin_name ) );
     357:         exit;
     358:       }
     359:       $this->plugin_id = basename( dirname( $this->plugin_file ) ) . '/' . basename( $this->plugin_file );
     360:       require_once(ABSPATH . 'wp-admin/includes/plugin.php');
     361:       if ( ! is_plugin_active( $this->plugin_id ) ) {
     362:         trigger_error( sprintf( __( 'Plugin %s is not an active plugin or is not installed in a subdirectory of %s.' ),
     363:           $this->plugin_name,
     364:           WP_PLUGIN_DIR
     365:         ));
     366:         exit;
     367:       }
     368:       register_deactivation_hook( $this->plugin_id, array( $this, 'deactivate' ) );
     369:     }
     370:
     371:     register_uninstall_hook( $this->plugin_id, array( $this->plugin_class, 'uninstall' ) );
     372:
     373:     $this->_plugin_settings = new Sidecar_Plugin_Settings( $this, $this->option_name );
     374:
     375:     /**
     376:      * Ask subclass to initialize plugin which includes admin pages
     377:      */
     378:     $this->initialize_plugin();
     379:
     380:   }
     381:
     382:   /**
     383:    * Delete the flag indicating that a post needs external files (CSS styles and JS scripts) for each
     384:    * shortcode we have in case the newly saved post now has changed the use of the shortcodes.
     385:    */
     386:   function _save_post( $post_id ) {
     387:     $this->initialize_shortcodes();
     388:     $shortcodes = $this->get_shortcodes();
     389:     if ( is_array( $shortcodes ) ) {
     390:       /**
     391:        * @var Sidecar_Shortcode $shortcode
     392:        */
     393:       foreach( $shortcodes as $shortcode )
     394:         $shortcode->delete_has_shortcode( $post_id );
     395:       /**
     396:        * Now load the post asynchronously via HTTP to pre-set the meta value for $this->HAS_SHORTCODE_KEY.
     397:        */
     398:       wp_remote_request( get_permalink( $post_id ), array( 'blocking' => false ) );
     399:     }
     400:   }
     401:
     402:   /**
     403:    * Used to check if we are on a plugin page that is asking about deletion.
     404:    *
     405:    * @return bool
     406:    */
     407:   function is_confirm_plugin_deletion() {
     408:     return $this->is_plugin_deletion()
     409:       && ! isset( $_POST['verify-delete'] );
     410:   }
     411:
     412:   /**
     413:    * Used to check if we are on a plugin page that is deleting (a) plugin(s).
     414:    *
     415:    * @return bool
     416:    */
     417:   function is_verified_plugin_deletion() {
     418:     return $this->is_plugin_deletion()
     419:       && isset( $_POST['verify-delete'] ) &&  '1' == $_POST['verify-delete'];
     420:   }
     421:
     422:   /**
     423:    * Used to check if we are a plugin page askin about deletion or processing deletion request.
     424:    *
     425:    * @return bool
     426:    */
     427:   function is_plugin_deletion() {
     428:     return $this->is_plugin_page()
     429:       && isset( $_GET['action'] ) &&  'delete-selected' == $_GET['action']
     430:       && isset( $_REQUEST['checked'] ) && count( $_REQUEST['checked'] );
     431:   }
     432:
     433:   /**
     434:    *
     435:    */
     436:   static function uninstall() {
     437:
     438:     /**
     439:      * @var Sidecar_Plugin_Base $plugin
     440:      */
     441:     $plugin = self::this();
     442:
     443:     /**
     444:      * Initialize it so we can ensure all properties are set in case $plugin->uninstall_plugin() needs them.
     445:      */
     446:     $plugin->initialize();
     447:
     448:     /**
     449:      * Delete settings
     450:      */
     451:     $plugin->delete_settings();
     452:
     453:     /*
     454:      * Call subclass' uninstall if applicable.
     455:      */
     456:     $plugin->uninstall_plugin();
     457:
     458:   //    /**
     459:   //     * Delete cron tasks
     460:   //     */
     461:   //    $next_run = wp_next_scheduled( $plugin->cron_key );
     462:   //    wp_unschedule_event( $next_run, $plugin->cron_key );
     463:
     464:   }
     465:
     466:   /**
     467:    * Used to check if we are in an activation callback on the Plugins page.
     468:    *
     469:    * @return bool
     470:    */
     471:   function is_plugin_page() {
     472:     global $pagenow;
     473:     return 'plugins.php' == $pagenow;
     474:   }
     475:
     476:   /**
     477:    * Used to check if we are in an activation callback on the Plugins page.
     478:    *
     479:    * @return bool
     480:    */
     481:   function is_plugin_page_action() {
     482:     return $this->is_plugin_page()
     483:       && isset( $_GET['action'] )
     484:       && isset( $_GET['plugin'] );
     485:   }
     486:
     487: //  /**
     488: //   * Used to check if we are activating a plugin.
     489: //   *
     490: //   * @return bool
     491: //   */
     492: //  function is_plugin_activation() {
     493: //    return $this->is_plugin_page_action()
     494: //      && 'activate' == $_GET['action'];
     495: //  }
     496:
     497:   /**
     498:    * This is used for the "activate_{$this->plugin_id}" hook
     499:    * when $this->is_plugin_page_action().
     500:    */
     501:   function _activate_plugin() {
     502:     $this->initialize();
     503:   }
     504:
     505:   /**
     506:    * This is used for the "activate_{$this->plugin_id}" hook
     507:    * when $this->is_plugin_page_action().
     508:    */
     509:   function _plugins_loaded() {
     510:     $this->initialize();
     511:   }
     512:
     513:   /**
     514:    * @return array|bool
     515:    */
     516:   function get_forms() {
     517:     foreach( $this->_forms as $form_name => $form )
     518:       if ( is_array( $form ) )
     519:         $this->_forms[$form_name] = $this->promote_form( $form );
     520:     return $this->_forms;
     521:   }
     522:
     523:   /**
     524:    * @return bool
     525:    */
     526:   function has_forms() {
     527:     return is_array( $this->_forms ) && count( $this->_forms );
     528:   }
     529:
     530:   /**
     531:    *
     532:    */
     533:   function _wp_print_styles() {
     534:       $localfile = 'css/style.css';
     535:     $args = apply_filters( "sidecar_print_{$this->plugin_name}_styles", array(
     536:       'name'  => "{$this->plugin_name}_style",
     537:       'path'  => "{$this->plugin_path}/{$localfile}",
     538:       'url'   => plugins_url( $localfile, $this->plugin_file ),
     539:     ));
     540:       if ( file_exists( $args['path'] ) )
     541:           wp_enqueue_style( $args['name'], $args['url'] );
     542:     }
     543:
     544:   /**
     545:    * @return bool
     546:    */
     547:   function has_admin_pages() {
     548:     return 0 < count( $this->_admin_pages );
     549:   }
     550:   /**
     551:    * @param string $form_name
     552:    *
     553:    * @return bool
     554:    */
     555:   function has_form( $form_name ) {
     556:     return isset( $this->_forms[$form_name] );
     557:   }
     558:
     559:   function initialize() {
     560:     if ( $this->_initialized )
     561:       return;
     562:
     563:     /*
     564:      * Avoid potential infinite loop.
     565:      */
     566:     $this->_initialized = true;
     567:
     568:     if ( ! $this->plugin_title )
     569:       /*
     570:        * Hope we can get something better in the subclass' $this->initialize()...
     571:        */
     572:       $this->plugin_title = ucwords( str_replace( '_', ' ', $this->plugin_name ) );
     573:
     574:     if ( ! $this->plugin_label )
     575:       /*
     576:        * Used for menu title for default
     577:        */
     578:       $this->plugin_label = $this->plugin_title;
     579:
     580:     if ( ! $this->plugin_file )
     581:       Sidecar::show_error( '%s->plugin_file must be set in an %s->initialize_admin() method', get_class( $this), get_class( $this) );
     582:
     583: //    $this->plugin_file_base = preg_replace( '#^(.*)-plugin\.php$#', '$1', $this->plugin_file );
     584:
     585:     if ( ! $this->plugin_path )
     586:       $this->plugin_path = dirname( $this->plugin_file );
     587:
     588:     if ( ! $this->plugin_slug )
     589:       $this->plugin_slug = str_replace( '_', '-', $this->plugin_name );
     590:
     591:     if ( ! $this->css_base )
     592:       $this->css_base = $this->plugin_slug;
     593:
     594:     $this->_plugin_settings->load_settings();
     595:
     596:   }
     597:
     598:   /**
     599:    * @return array
     600:    */
     601:   function get_default_settings_values() {
     602:     if ( ! isset( $this->_default_settings_values ) ) {
     603:       $default_settings_values = array();
     604:       foreach( $this->get_forms() as $form ) {
     605:         /**
     606:          * @var Sidecar_Form $form
     607:          */
     608:         $default_settings_values[$form->form_name] = $form->get_default_settings_values();
     609:       }
     610:       $this->_default_settings_values = $default_settings_values;
     611:     }
     612:     return $this->_default_settings_values;
     613:   }
     614:
     615:   /**
     616:    * @param array $settings_values
     617:    */
     618:   function update_settings_values( $settings_values ) {
     619:     $this->get_settings()->set_values( $settings_values );
     620:   }
     621:
     622:   /**
     623:    * @param string|Sidecar_Form $form
     624:    * @param string $setting_name
     625:    * @return array
     626:    */
     627:   function get_form_settings_value( $form, $setting_name ) {
     628:     if ( ! $form instanceof Sidecar_Form )
     629:       $form = $this->get_form( $form );
     630:     return $this->get_form_settings( $form )->get_setting( $setting_name );
     631:
     632:   }
     633:
     634:   /**
     635:    * @param string|Sidecar_Form $form
     636:    * @param string $setting_name
     637:    * @param string $value
     638:    * @return array
     639:    */
     640:   function update_form_settings_value( $form, $setting_name, $value ) {
     641:     if ( ! $form instanceof Sidecar_Form )
     642:       $form = $this->get_form( $form );
     643:     return $form->update_settings_value( $setting_name, $value );
     644:   }
     645:
     646:   /**
     647:    * @return bool
     648:    */
     649:   function has_required_settings() {
     650:     $has_required_settings = true;
     651:     if ( ! $this->_initialized )
     652:       $this->initialize();
     653:     if ( $this->has_forms() ) {
     654:       /** @var Sidecar_Form $form */
     655:       foreach( $this->get_forms() as $form_name => $form ) {
     656:         $form_settings = $this->get_form_settings( $form->form_name );
     657:         $form_settings->set_required_fields( $form->get_required_field_names() );
     658:         if ( ! $form_settings->has_required_fields() ) {
     659:           $has_required_settings = false;
     660:           break;
     661:         }
     662:       }
     663:     }
     664:     if ( method_exists( $this, 'filter_has_required_settings' ) ) {
     665:       $has_required_settings = $this->filter_has_required_settings( $has_required_settings, $this->get_settings() );
     666:     }
     667:     return $has_required_settings;
     668:   }
     669:
     670:
     671:   /**
     672:    * @param Sidecar_Settings $settings
     673:    */
     674:   function set_settings( $settings ) {
     675:     $this->_plugin_settings = $settings;
     676:   }
     677:
     678:   /**
     679:    * @return Sidecar_Plugin_Settings
     680:    */
     681:   function get_settings() {
     682:     if ( ! $this->_initialized )
     683:       $this->initialize();
     684:     return $this->_plugin_settings;
     685:   }
     686:
     687:   /**
     688:    * @param string|Sidecar_Form $form
     689:    * @return mixed|Sidecar_Form_Settings
     690:    */
     691:   function get_form_field_values( $form ) {
     692:     return $this->get_form_settings( $form );
     693:   }
     694:
     695:   /**
     696:    * @param string|Sidecar_Form $form
     697:    * @param string $field_name
     698:    * @return mixed|Sidecar_Form_Settings
     699:    */
     700:   function get_form_field_value( $form, $field_name ) {
     701:     return $this->get_form_settings( $form )->get_values();
     702:   }
     703:
     704:   /**
     705:    * @param string|Sidecar_Form $form
     706:    * @param array $field_values
     707:    * @return mixed|Sidecar_Form_Settings
     708:    */
     709:   function set_form_field_values( $form, $field_values ) {
     710:     $this->set_form_settings( $form, $field_values );
     711:   }
     712:
     713:   /**
     714:    * @param string|Sidecar_Form $form
     715:    * @return mixed|Sidecar_Form_Settings
     716:    */
     717:   function get_form_settings( $form ) {
     718:     if ( ! $form instanceof Sidecar_Form )
     719:       $form = $this->get_form( $form );
     720:     return $this->get_settings()->get_setting( $form->form_name );
     721:   }
     722:
     723:   /**
     724:    * @param string|Sidecar_Form $form
     725:    * @param mixed $setting_value
     726:    * @return mixed|Sidecar_Form_Settings
     727:    */
     728:   function set_form_settings( $form, $setting_value ) {
     729:     if ( ! $form instanceof Sidecar_Form )
     730:       $form = $this->get_form( $form );
     731:     $this->get_settings()->set_setting( $form->form_name, $setting_value );
     732:   }
     733:
     734:   /**
     735:    * Delete the persisted settings on disk.
     736:    *
     737:    * @return bool
     738:    */
     739:   function delete_settings() {
     740:     $this->get_settings()->delete_settings();
     741:   }
     742:
     743:   /**
     744:    *
     745:    */
     746:   function _wp_loaded() {
     747:     if ( is_admin() ) {
     748:       add_action( 'admin_notices',       array( $this, '_admin_notices' ) );
     749:       if ( $this->is_plugin_page() ) {
     750:         add_filter( 'plugin_action_links', array( $this, '_plugin_action_links' ), 10, 2 );
     751:         add_filter( 'plugin_row_meta',     array( $this, '_plugin_meta_links' ), 10, 2 );
     752:       }
     753:     } else {
     754:       $shortcodes = method_exists( $this->plugin_class, 'initialize_shortcodes' );
     755:       $template = method_exists( $this, 'initialize_template' );
     756:       if ( $shortcodes || $template ) {
     757:         // @todo Should we always initialize or only when we need it?
     758:         $this->initialize();
     759:         if ( $shortcodes )
     760:           $this->initialize_shortcodes();
     761:         if ( $template )
     762:           $this->initialize_template();
     763:         add_filter( 'the_content', array( $this, '_the_content' ), -1000 );
     764:       }
     765:     }
     766:   }
     767:   /**
     768:    */
     769:   function _admin_notices() {
     770:     if ( $this->needs_settings && ! $this->has_required_settings() && $this->is_plugin_page() && ! $this->is_confirm_plugin_deletion() ) {
     771:       $icon_html = $this->has_url( 'logo_icon' ) ? "<span class=\"sidecar-logo-icon\"></span><img src=\"{$this->logo_icon_url}\" /></span>" : '';
     772:       $message = sprintf( __( 'The <em>%s</em> plugin is now activated. Please configure it\'s <a href="%s"><strong>settings</strong></a>.', 'sidecar' ),
     773:       $this->plugin_title,
     774:       $this->get_settings_url()
     775:     );
     776:     $html = <<<HTML
     777: <div id="message" class="error settings-error">
     778:     <p>{$icon_html}{$message}</p>
     779: </div>
     780: HTML;
     781:     echo $html;
     782:     }
     783:   }
     784:
     785:   /**
     786:    * @param $links
     787:    * @param $file
     788:    *
     789:    * @return array
     790:    */
     791:   function _plugin_action_links( $links, $file ){
     792:     if ( $file == $this->plugin_id  ) {
     793:       $url = $this->get_settings_url();
     794:       $link_text = __( 'Settings', 'sidecar' );
     795:       $links[] = "<a href=\"{$url}\">{$link_text}</a>";
     796:     }
     797:     return $links;
     798:   }
     799:
     800:   /**
     801:    * @return bool|string|void
     802:    */
     803:   function get_settings_url() {
     804:     $settings_url = false;
     805:     if ( $settings_page = $this->get_admin_page( 'settings' ) ) {
     806:       $settings_page->initialize();
     807:       $settings_url = $this->get_admin_page( 'settings' )->get_page_url( null );
     808:     }
     809:     if ( method_exists( $this, 'filter_settings_url' ) )
     810:       $settings_url = $this->filter_settings_url( $settings_url );
     811:     return $settings_url;
     812:   }
     813:   /**
     814:    * @param array $links
     815:    * @param string $file
     816:    *
     817:    * @return array
     818:    */
     819:   function _plugin_meta_links( $links, $file ){
     820:     if ( $file == $this->plugin_id ) {
     821:       foreach( $this->_meta_links as $link_text => $link ) {
     822:         $title = isset( $link['title'] ) ? " title=\"{$link['title']}\"" : '';
     823:         $links[] = "<a target=\"_blank\" href=\"{$link['url']}\"{$title}>{$link_text}</a>";
     824:       }
     825:     }
     826:     return $links;
     827:   }
     828:
     829:   /**
     830:    * @param $content
     831:    */
     832:   function _the_content( $content ) {
     833:     $shortcodes = $this->get_shortcodes();
     834:     if ( is_array( $shortcodes ) )
     835:       /**
     836:        * @var Sidecar_Shortcode $shortcode
     837:        */
     838:       foreach( $shortcodes as $shortcode_name => $shortcode ) {
     839:         if ( method_exists( $this, 'initialize_shortcode' ) ) {
     840:           $this->initialize_shortcode( $shortcode );
     841:         }
     842:         add_shortcode( $shortcode_name, array( $shortcode, 'do_shortcode' ) );
     843:         /**
     844:          * Now get each shortcode to monitor for it's own use.
     845:          */
     846:         $shortcode->add_the_content_filter();
     847:       }
     848:     return $content;
     849:   }
     850:
     851:   function _init() {
     852: //    add_action( 'cron_schedules', array( $this, '_cron_schedules' ) );
     853: //    add_action( 'cron', array( $this, '_cron' ) );
     854:     /**
     855:      * @todo Figure out how to load this only if needed
     856:      */
     857:     load_plugin_textdomain( $this->plugin_slug, false, '/' . basename( dirname( $this->plugin_file ) ) . '/languages' );
     858:
     859:     if ( is_admin() ) {
     860:
     861:       /**
     862:        * @todo Can we initialize only what's needed if it is not using one of the pages
     863:        */
     864:       if (true) {
     865:         /**
     866:          * Now set all the defaults
     867:          */
     868:         $this->initialize();
     869:
     870:         /**
     871:          * Call the subclass and ask it to initialize itself
     872:          */
     873:         $this->_initialize_admin();
     874:
     875: //        if ( $this->plugin_version )
     876: //          $this->plugin_title .= sprintf( ' v%s', $this->plugin_version );
     877:
     878:       }
     879:     }
     880:   }
     881:   /**
     882:    * @param string $to 'api_vars' or 'fields'
     883:    * @param Sidecar_Form $form
     884:    * @param array $fields
     885:    * @return array
     886:    */
     887:   function transform_form_fields_to( $to, $form, $fields ) {
     888:     $to = rtrim( $to, 's' );
     889:     $field_objects = $form->get_fields();
     890:     foreach( $fields as $field_name => $value ) {
     891:       if ( isset( $field_objects[$field_name] ) ) {
     892:         if ( $field_name != ( $new_name = $field_objects[$field_name]->$to ) ) {
     893:           if ( $new_name )  // If false then it's not an API var
     894:             $fields[$new_name] = $value;
     895:           unset( $fields[$field_name] );
     896:         }
     897:       }
     898:     }
     899:     return $fields;
     900:   }
     901:
     902:   /**
     903:    * @param string $to 'api_vars' or 'settings'
     904:    * @param Sidecar_Shortcode $shortcode
     905:    * @param array $attributes
     906:    * @return array
     907:    */
     908:   function transform_shortcode_attributes_to( $to, $shortcode, $attributes ) {
     909:     $to = rtrim( $to, 's' );
     910:     $attribute_objects = $shortcode->get_attributes();
     911:     foreach( $attributes as $attribute_name => $attribute ) {
     912:       if ( isset( $attribute_objects[$attribute_name] ) ) {
     913:         if ( $attribute_name != ( $new_name = $attribute_objects[$attribute_name]->$to ) ) {
     914:           $attributes[$new_name] = $attribute;
     915:           unset( $attributes[$attribute_name] );
     916:         }
     917:       }
     918:     }
     919:     return $attributes;
     920:   }
     921:
     922:   /**
     923:    * @param Sidecar_Form $form
     924:    */
     925:   function initialize_form( $form ) {
     926:     // Only here to keep PhpStorm from complaining that it's not defined.
     927:   }
     928:
     929:   /**
     930:    *
     931:    */
     932:   function initialize_shortcodes() {
     933:     // Only here to keep PhpStorm from complaining that it's not defined.
     934:   }
     935:
     936:   /**
     937:    *
     938:    */
     939:   function initialize_postback() {
     940:     // Only here to keep PhpStorm from complaining that it's not defined.
     941:   }
     942:
     943:   /**
     944:    *
     945:    */
     946:   function initialize_template() {
     947:     // Only here to keep PhpStorm from complaining that it's not defined.
     948:   }
     949:
     950:   /**
     951:    *
     952:    */
     953:   function uninstall_plugin() {
     954:     // Only here to keep PhpStorm from complaining that it's not defined.
     955:   }
     956:
     957:   /**
     958:    * @throws Exception
     959:    */
     960:   function initialize_plugin() {
     961:     throw new Exception( 'Class ' . get_class($this) . ' [subclass of ' . __CLASS__ . '] must define an initialize_plugin() method.' );
     962:   }
     963:
     964:   /**
     965:    * @throws Exception
     966:    */
     967:   function initialize_admin() {
     968:     throw new Exception( 'Class ' . get_class($this) . ' [subclass of ' . __CLASS__ . '] must define an initialize_admin() method.' );
     969:   }
     970:
     971:   /**
     972:    * @param Sidecar_Admin_Page $admin_page
     973:    * @throws Exception
     974:    */
     975:   function initialize_admin_page( $admin_page ) {
     976:     throw new Exception( 'Class ' . get_class($this) . ' [subclass of ' . __CLASS__ . '] must define an initialize_admin_page() method.' );
     977:   }
     978:
     979:   /**
     980:    * @param Sidecar_Shortcode $shortcode
     981:    * @throws Exception
     982:    */
     983:   function initialize_shortcode( $shortcode ) {
     984:     throw new Exception( 'Class ' . get_class($this) . ' [subclass of ' . __CLASS__ . '] must define an initialize_shortcode() method.' );
     985:   }
     986:
     987:   /**
     988:    * @param Sidecar_Shortcode $shortcode
     989:    * @param array() $attributes
     990:    * @param string $content
     991:    *
     992:    * @throws Exception
     993:    * @return string
     994:    */
     995:   function do_shortcode( $shortcode, $attributes, $content = null ) {
     996:     if (1) // Only here to keep PhpStorm from flagging the return as an error.
     997:       throw new Exception( 'Class ' . get_class($this) . ' [subclass of ' . __CLASS__ . '] must define an do_shortcode() method.' );
     998:     return '';
     999:   }
    1000:
    1001:   /**
    1002:    * @param array $args
    1003:    *
    1004:    * @return mixed
    1005:    */
    1006:   function add_default_button( $args = array() ) {
    1007:     /**
    1008:      * @var Sidecar_Form
    1009:      */
    1010:     $form = isset( $args['form'] ) ? $args['form'] : end( $this->_forms );
    1011:     return $form->add_button( 'save', __( 'Save Settings', 'sidecar' ), $args );
    1012:   }
    1013:
    1014:   /**
    1015:    * @param string $button_name
    1016:    * @param string $button_text
    1017:    * @param array $args
    1018:    *
    1019:    * @return mixed
    1020:    */
    1021:   function add_button( $button_name, $button_text, $args = array() ) {
    1022:     /**
    1023:      * @var Sidecar_Form
    1024:      */
    1025:     $form = isset( $args['form'] ) ? $args['form'] : end( $this->_forms );
    1026:     return $form->add_button( $button_name, $button_text, $args );
    1027:   }
    1028:
    1029:   /**
    1030:    * @param       $page_name
    1031:    * @param array $args
    1032:    *
    1033:    * @return mixed
    1034:    */
    1035:   function add_admin_page( $page_name, $args = array() ) {
    1036:     /**
    1037:      * Give the admin page access back to this plugin.
    1038:      */
    1039:     $args['plugin'] = $this;
    1040:     /**
    1041:      * @var Sidecar_Admin_Page $admin_page
    1042:      */
    1043:     $this->_admin_pages[$page_name] = new Sidecar_Admin_Page( $page_name, $args );
    1044:   }
    1045:
    1046:   /**
    1047:    * @param string $link_text
    1048:    * @param string $url
    1049:    * @param array $args
    1050:    */
    1051:   function add_meta_link( $link_text, $url, $args = array() ) {
    1052:     $args['link_text'] = $link_text;
    1053:     $args['url'] = isset( $this->_urls[$url] ) ? $this->_urls[$url]['url_template'] : $url;
    1054:     $this->_meta_links[$link_text] = $args;
    1055:   }
    1056:
    1057:   /**
    1058:    * @param string $page_name
    1059:    *
    1060:    * @return Sidecar_Admin_Page
    1061:    */
    1062:   function get_admin_page( $page_name ) {
    1063:     $page_slug = preg_replace( "#^{$this->plugin_slug}-(.*)$#", '$1', $page_name );
    1064:     return isset( $this->_admin_pages[$page_slug] ) ? $this->_admin_pages[$page_slug] : false;
    1065:   }
    1066:
    1067:   /**
    1068:    *
    1069:    */
    1070:   function add_default_shortcode() {
    1071:     $this->add_shortcode( $this->plugin_slug );
    1072:   }
    1073:
    1074:   /**
    1075:    * @param       $shortcode_name
    1076:    * @param array $args
    1077:    */
    1078:   function add_shortcode( $shortcode_name, $args = array() ) {
    1079:     $args['plugin'] = $this;
    1080:     $this->_shortcodes[ $shortcode_name ] = new Sidecar_Shortcode( $shortcode_name, $args );
    1081:   }
    1082:
    1083:   /**
    1084:    * @return array|bool
    1085:    */
    1086:   function get_shortcodes() {
    1087:     return $this->_shortcodes;
    1088:   }
    1089:
    1090:   /**
    1091:    * @param bool|string $shortcode_name
    1092:    *
    1093:    * @return Sidecar_Shortcode
    1094:    */
    1095:   function get_shortcode( $shortcode_name = false ) {
    1096:     $shortcode = false;
    1097:
    1098:     if ( ! $shortcode_name )
    1099:       $shortcode_name = $this->plugin_slug; // This is the 'default' shortcode
    1100:
    1101:     if ( ! isset( $this->_shortcodes[$shortcode_name] ) ) {
    1102:       trigger_error( sprintf( __( 'Need to call %s->initialize_shortcodes() before using %s->get_shortcode().' ), $this->plugin_class, $this->plugin_class ) );
    1103:     } else {
    1104:       /**
    1105:        * @var Sidecar_Shortcode $shortcode
    1106:        */
    1107:       $shortcode = $this->_shortcodes[$shortcode_name];
    1108:       if ( ! $shortcode->initialized ) {
    1109:         $this->initialize_shortcode($shortcode);
    1110:         $shortcode->initialized = true;
    1111:       }
    1112:     }
    1113:     return $shortcode;
    1114:   }
    1115:
    1116:   /**
    1117:    * @param bool|string $shortcode_name
    1118:    *
    1119:    * @return bool|Sidecar_Shortcode
    1120:    */
    1121:   function get_shortcode_attributes( $shortcode_name = false ) {
    1122:     $shortcode = $this->get_shortcode($shortcode_name);
    1123:     return $shortcode ? $shortcode->get_attributes() : false;
    1124:   }
    1125:
    1126: //  /**
    1127: //   * @return mixed
    1128: //   */
    1129: //  protected function _get_subclass_filename() {
    1130: //    $subclass = new ReflectionObject( $this );
    1131: //    return $subclass->getFileName();
    1132: //  }
    1133: //
    1134: //  /**
    1135: //   * @param $schedules
    1136: //   *
    1137: //   * @return mixed
    1138: //   */
    1139: //  function _cron_schedules( $schedules ) {
    1140: //      $schedules['fifteenseconds'] = array( 'interval' => 15, 'display' => __( 'Once Every Fifteen Seconds' ) );
    1141: //      return $schedules;
    1142: //  }
    1143:
    1144: //  /**
    1145: //   * @return bool
    1146: //   */
    1147: //  function _cron() {
    1148: //    return true;
    1149: //  }
    1150:
    1151: //  function _deactivate() {
    1152: //      /**
    1153: //       * Unschedule cron
    1154: //       */
    1155: //      $next_run = wp_next_scheduled( $this->cron_key );
    1156: //      wp_unschedule_event( $next_run, $this->cron_key );
    1157: //  }
    1158:
    1159:   /**
    1160:    * @param string $url_name
    1161:    * @param string $url_template
    1162:    * @param array $args
    1163:    */
    1164:   function register_url( $url_name, $url_template, $args = array() ) {
    1165:     $args['url_name'] = $url_name;
    1166:     $args['url_template'] = $url_template;
    1167:     if ( ! isset( $args['url_vars'] ) )
    1168:       $args['url_vars'] = false;
    1169:     $this->_urls[$url_name] = $args;
    1170:   }
    1171:
    1172:   /**
    1173:    * @param string $url_name
    1174:    *
    1175:    * @return bool
    1176:    */
    1177:   function has_url( $url_name ) {
    1178:     return isset( $this->_urls[$url_name] );
    1179:   }
    1180:   /**
    1181:    * Get by name a previously registered URL with optional variable value replacement
    1182:    *
    1183:    * @param             $url_name
    1184:    * @param mixed|array $values
    1185:      * @example:
    1186:    *
    1187:    *    $this->register_url( 'my_named_url', 'https://example.com/?item={item_id}&color={color}' );
    1188:    *    $this->get_url( 'my_named_url', 1234, 'red' );
    1189:      *    $this->get_url( 'my_named_url', array( 2345, 'red' ) );
    1190:      *    $this->get_url( 'my_named_url', array( 'color' => 'red', 'item_id' => 3456 ) );
    1191:    *
    1192:    * @return string
    1193:    */
    1194:   function get_url( $url_name, $values = array() ) {
    1195:     if ( ! is_array( $values ) ) {
    1196:       /**
    1197:        * $values passed as additional function parameters instead of as single array of parameters.
    1198:        */
    1199:       $values = func_get_args();
    1200:       array_shift( $values );
    1201:     }
    1202:     $url = $this->_urls[$url_name]['url_template'];
    1203:     if ( is_array( $values ) ) {
    1204:       $vars = $this->_urls[$url_name]['url_vars'];
    1205:       if ( ! $vars ) {
    1206:         preg_match_all( '#([^{]+)\{([^}]+)\}#', $url, $matches );
    1207:         $vars = $matches[2];
    1208:       }
    1209:       if ( is_numeric( key($values) ) ) {
    1210:         /**
    1211:          * The $values array contains name/value pairs.
    1212:          */
    1213:         foreach( $values as $name => $value ) {
    1214:           if ( isset( $vars[0] ) )
    1215:             $url = str_replace( "{{$vars[0]}}", $value, $url );
    1216:         }
    1217:       } else {
    1218:         /**
    1219:          * The $values array just contains values in same order as the vars specified in the URL.
    1220:          */
    1221:         foreach( $vars as $name ) {
    1222:           if ( isset( $values[$name] ) )
    1223:             $url = str_replace( "{{$name}}", $values[$name], $url );
    1224:         }
    1225:       }
    1226:     }
    1227:     return $url;
    1228:     }
    1229:
    1230:   /**
    1231:    * @param $url_name
    1232:    *
    1233:    * @return bool
    1234:    */
    1235:   function get_link_text( $url_name ) {
    1236:     return isset( $this->_urls[$url_name]['link_text'] ) ? $this->_urls[$url_name]['link_text'] : false;
    1237:     }
    1238:
    1239:   /**
    1240:    * @param $url_name
    1241:    *
    1242:    * @return bool
    1243:    */
    1244:   function get_link_class( $url_name ) {
    1245:     return isset( $this->_urls[$url_name]['link_class'] ) ? " class=\"{$this->_urls[$url_name]['link_class']}\"" : false;
    1246:     }
    1247:
    1248:   /**
    1249:    * @param string $image_name
    1250:    * @param string $image_url
    1251:    * @param array $args
    1252:    */
    1253:   function register_image( $image_name, $image_url, $args = array() ) {
    1254:     $args['image_name'] = $image_name;
    1255:     $args['image_url'] = $image_url;
    1256:     if ( ! isset( $args['url_vars'] ) )
    1257:       $args['image_vars'] = false;
    1258:     $this->_images[$image_name] = $args;
    1259:   }
    1260:
    1261:   /**
    1262:    * @param string $image_name
    1263:    *
    1264:    * @return bool
    1265:    */
    1266:   function has_image( $image_name ) {
    1267:     return isset( $this->_images[$image_name] );
    1268:   }
    1269:
    1270:   /**
    1271:    * Get by name a previously registered image with optional variable value replacement
    1272:    *
    1273:    * @param             $image_name
    1274:    * @param mixed|array $values
    1275:      * @example:
    1276:    *
    1277:    *    $this->register_image( 'my_logo', 'my-logo.png' );
    1278:    *    echo $this->my_logo_image_url;
    1279:    *
    1280:    *    $this->register_image( 'my_icon', '{icon_type}.png' );
    1281:    *    echo $this->get_image_url( 'my_icon', 'pdf' );
    1282:    *    echo $this->get_image_url( 'my_icon', array('pdf') );
    1283:    *    echo $this->get_image_url( 'my_icon', array('icon_type' => 'pdf') );
    1284:    *
    1285:    * @return string
    1286:    */
    1287:   function get_image_url( $image_name, $values = array() ) {
    1288:     if ( ! is_array( $values ) ) {
    1289:       /**
    1290:        * $values passed as additional function parameters instead of as single array of parameters.
    1291:        */
    1292:       $values = func_get_args();
    1293:       array_shift( $values );
    1294:     }
    1295:     $image_url = $this->_images[$image_name]['image_url'];
    1296:     if ( is_array( $values ) ) {
    1297:       $vars = $this->_images[$image_name]['image_vars'];
    1298:       if ( ! $vars ) {
    1299:         preg_match_all( '#\{([^}]+)\}#', $image_url, $matches );
    1300:         $vars = $matches[1];
    1301:       }
    1302:       if ( is_numeric( key($values) ) ) {
    1303:         /**
    1304:          * The $values array contains name/value pairs.
    1305:          */
    1306:         foreach( $values as $value ) {
    1307:           $image_url = str_replace( "{{$vars[0]}}", $value, $image_url );
    1308:         }
    1309:       } else {
    1310:         /**
    1311:          * The $values array just contains values in same order as the vars specified in the image.
    1312:          */
    1313:         foreach( $vars as $name ) {
    1314:           $image_url = str_replace( "{{$name}}", $values[$name], $image_url );
    1315:         }
    1316:       }
    1317:     }
    1318:     if ( ! preg_match( '#^https?//#', $image_url ) ) {
    1319:       $image_url = plugins_url( "/images/{$image_url}", $this->plugin_file );
    1320:     }
    1321:     return $image_url;
    1322:     }
    1323: //  /**
    1324: //   * @return bool
    1325: //   */
    1326: //  function is_authenticated() {
    1327: //      return false;
    1328: //  }
    1329:
    1330:   /**
    1331:    * Echo the current or specified form.
    1332:    *
    1333:    * @param bool|Sidecar_Form $form
    1334:    *
    1335:    * @return Sidecar_Form
    1336:    */
    1337:   function the_form( $form = false ) {
    1338:     if ( ! $form )
    1339:       $form = $this->get_current_form();
    1340:     return $form->the_form();
    1341:     }
    1342:
    1343:   /**
    1344:    * @param   array   $form
    1345:    * @return  Sidecar_Form
    1346:    */
    1347:   function promote_form( $form ) {
    1348:     /**
    1349:      * @var array $form
    1350:      */
    1351:     $form_name = $form['form_name'];
    1352:     $form['plugin'] = $this;
    1353:
    1354:     if ( ! isset( $form['admin_page'] ) )
    1355:       $form['admin_page'] = end( $this->_admin_pages );
    1356:
    1357:     /**
    1358:      * @var array|Sidecar_Form $form
    1359:      */
    1360:     $form = $this->_forms[$form_name] = new Sidecar_Form( $form_name, $form );
    1361:
    1362:     $this->set_current_form( $form );
    1363:     $this->initialize_form( $form );
    1364:     $form->initialize();
    1365:     return $form;
    1366:   }
    1367:   /**
    1368:    * @param array $args
    1369:    */
    1370:   function the_form_section( $args ) {
    1371:     if ( ! empty( $args['section']->section_text ) )
    1372:       echo $args['section']->section_text;
    1373:   }
    1374:
    1375:   /**
    1376:    * @param $args
    1377:    *
    1378:    * @return Sidecar_Field
    1379:    */
    1380:   function get_form_field( $field_name, $form_name ) {
    1381:     /**
    1382:      * @var Sidecar_Form $form
    1383:      */
    1384:     $form = $this->get_form( $form_name );
    1385:     return $form ? $form->get_field( $field_name ) : false;
    1386:   }
    1387:   /**
    1388:    * @param array $args
    1389:    */
    1390:   function get_form_field_html( $field_name, $form_name ) {
    1391:     /**
    1392:      * @var Sidecar_Field $field
    1393:      */
    1394:     $field = $this->get_form_field( $field_name, $form_name );
    1395:     return $field->get_html();
    1396:   }
    1397:
    1398:   /**
    1399:    * @param array $args
    1400:    */
    1401:   function the_form_field( $field_name, $form_name ) {
    1402:     echo $this->get_form_field_html( $field_name, $form_name );
    1403:   }
    1404:
    1405:   /**
    1406:    * @param string $shortcode_name
    1407:    * @return bool
    1408:    */
    1409:   function has_shortcode( $shortcode_name ) {
    1410:     return isset( $this->_shortcodes[$shortcode_name] );
    1411:   }
    1412:   /**
    1413:    * @param   string|Sidecar_Form $form
    1414:    * @return  array|Sidecar_Form
    1415:    */
    1416:   function get_form( $form ) {
    1417:     /*
    1418:      * Could be a string or already Sidecar_Form.
    1419:      */
    1420:     $form = is_string( $form ) && isset( $this->_forms[$form] ) ? $this->_forms[$form] : $form;
    1421:     if ( is_array( $form ) )
    1422:       $form = $this->promote_form( $form );
    1423:     return is_object( $form ) ? $form : false;
    1424:   }
    1425:   /**
    1426:    * @param string  $form_name
    1427:    * @param array   $args
    1428:    */
    1429:   function add_form( $form_name, $args = array() ) {
    1430:     $args['form_name'] = $form_name;
    1431:     if ( ! isset( $args['requires_api'] ) && 'account' == $form_name )
    1432:       $args['requires_api'] = true;
    1433:     $this->_forms[$form_name] = $args;
    1434:     $this->_plugin_settings->register_form_settings( $form_name );
    1435:   }
    1436:
    1437:   /**
    1438:    * @param       $form_name
    1439:    * @param array $args
    1440:    */
    1441:
    1442:   /**
    1443:    * @param string  $form_name
    1444:    * @param array   $args
    1445:    * @return Sidecar_Field
    1446:    */
    1447:   function add_field( $form_name, $args = array() ) {
    1448:     /**
    1449:      * @var Sidecar_Form
    1450:      */
    1451:     $form = isset( $args['form'] ) ? $args['form'] : end( $this->_forms );
    1452:     return $form->add_field( $form_name, $args );
    1453:   }
    1454:
    1455:   /**
    1456:    * This function name will be more logical to people, but underneath it is 'has_grant()'
    1457:    *
    1458:    * @return bool
    1459:    */
    1460:   function is_authenticated() {
    1461:     return $this->has_grant();
    1462:   }
    1463:
    1464:   /**
    1465:    * Determines if the currently stored settings contain a grant to access the API.
    1466:    *
    1467:    * @return bool
    1468:    */
    1469:   function has_grant() {
    1470:     $has_grant = false;
    1471:     if ( $this->needs_grant() ) {
    1472:       $auth_form_values = $this->get_auth_form()->get_settings_values();
    1473:       $has_grant = $this->get_api()->is_grant( $auth_form_values );
    1474:     }
    1475:     return $has_grant;
    1476:   }
    1477:
    1478:   /**
    1479:    * @return bool
    1480:    */
    1481:   function needs_grant() {
    1482:     return $this->has_api();
    1483:   }
    1484:
    1485:   /**
    1486:    * Get grant from the currently stored account settings
    1487:    *
    1488:    * @return array
    1489:    */
    1490:   function get_grant() {
    1491:     /**
    1492:      * @var RESTian_Auth_Provider_Base $auth_provider
    1493:      */
    1494:     $auth_provider = $this->get_api()->get_auth_provider();
    1495:     return $auth_provider->extract_grant( $this->get_auth_form()->get_settings_values() );
    1496:   }
    1497:
    1498:   /**
    1499:    * Get credentials from the currently stored account settings
    1500:    *
    1501:    * @return array
    1502:    */
    1503:   function get_credentials() {
    1504:     /**
    1505:      * @var RESTian_Auth_Provider_Base $auth_provider
    1506:      */
    1507:     $auth_provider = $this->get_api()->get_auth_provider();
    1508:     return $auth_provider->extract_credentials( $this->get_auth_form()->get_settings_values() );
    1509:   }
    1510:
    1511:   /**
    1512:    * @return Sidecar_Admin_Page
    1513:    */
    1514:   function get_current_admin_page() {
    1515:     if ( ! isset( $this->_current_admin_page ) ) {
    1516:       if ( ! isset( $_GET['page'] ) || ! is_admin() ) {
    1517:         $this->_current_admin_page = false;
    1518:       } else {
    1519:         if ( isset( $_GET['tab'] ) ) {
    1520:           $tab = $_GET['tab'];
    1521:         } else {
    1522:           $page = $this->get_admin_page($_GET['page']);
    1523:           if ( $page && method_exists( $page, 'get_default_tab' ) ) {
    1524:             $tab = $page->get_default_tab()->tab_slug;
    1525:           }
    1526:         };
    1527:         /**
    1528:          * If we have a $_GET['page'] then is should be "{$plugin_slug}-{$page_slug}"
    1529:          * Scan through the array to find it.
    1530:          */
    1531:         foreach( array_keys( $this->_admin_pages ) as $admin_page_slug ) {
    1532:           if ( "{$this->plugin_slug}-{$admin_page_slug}" == $_GET['page'] ) {
    1533:             $this->_current_admin_page = $this->get_admin_page( $admin_page_slug );
    1534:             break;
    1535:           }
    1536:         }
    1537:       }
    1538:     }
    1539:     return $this->_current_admin_page;
    1540:   }
    1541:
    1542:   /**
    1543:    * @param Sidecar_Admin_Page $current_admin_page
    1544:    */
    1545:   function set_current_admin_page( $current_admin_page ) {
    1546:     $this->_current_admin_page = $current_admin_page;
    1547:   }
    1548:
    1549:   /**
    1550:    * @return Sidecar_Form
    1551:    */
    1552:   function get_current_form() {
    1553:     return $this->_current_form;
    1554:   }
    1555:
    1556:   /**
    1557:    * @param Sidecar_Admin_Page $current_form
    1558:    */
    1559:   function set_current_form( $current_form ) {
    1560:     $this->_current_form = $current_form;
    1561:   }
    1562:
    1563:   /**
    1564:    * Capture values from form but cause update_option() to be bypassed. We'll update in the shutdown hook.
    1565:    *
    1566:    * @param array $new_value
    1567:    * @param array $old_value
    1568:    * @return array
    1569:    */
    1570:   function _pre_update_option( $new_value, $old_value ) {
    1571:     /**
    1572:      * This only going to be saving one form's worth of data yet the settings can have many forms, like:
    1573:      *
    1574:      *    $settings = array(
    1575:      *      'form1' => array( ... ),
    1576:      *      'form2' => array( ... ),
    1577:      *      'form3' => array( ... ),
    1578:      *    );
    1579:      *
    1580:      * So the next 3 lines grab all the old values of the other forms and uses the new values for this form.
    1581:      */
    1582:     if ( ! isset( $new_value['_sidecar_form_meta'] ) ) {
    1583:       $return_value = $new_value;
    1584:     } else {
    1585:       $form_name = $new_value['_sidecar_form_meta']['form'];
    1586:       $this->set_form_settings( $form_name, $new_value[$form_name] );
    1587:       $return_value = $this->get_settings()->get_data();
    1588:     }
    1589:
    1590:     return $return_value;
    1591:   }
    1592:   /**
    1593:
    1594:    * @todo Decide if trigger_error() is the best for error messages
    1595:    */
    1596:   function _activate() {
    1597:     if ( ! $this->_initialized )
    1598:       $this->initialize();
    1599:
    1600:     if ( method_exists( $this, 'activate' ) )
    1601:       $this->activate();
    1602:
    1603:     global $wp_version;
    1604:     if ( version_compare( $wp_version, $this->min_wp, '<' ) ) {
    1605:       deactivate_plugins( basename( $this->plugin_file ) );
    1606:       $msg = __( 'Your site needs to be running WordPress %s or later in order to use %s.', 'sidecar' );
    1607:       trigger_error( sprintf( $msg, $this->min_wp, $this->plugin_title ), E_USER_ERROR );
    1608:     } if ( version_compare( PHP_VERSION, $this->min_php, '<' ) ) {
    1609:       deactivate_plugins( basename( $this->plugin_file ) );
    1610:       $msg = __( 'Your site needs to be running PHP %s or later in order to use %s.', 'sidecar' );
    1611:       trigger_error( sprintf( $msg, $this->min_php, $this->plugin_title ), E_USER_ERROR );
    1612:     } else {
    1613:       // @todo Add simplified support for cron when we see a use-case for it.
    1614:       //if ( ! wp_next_scheduled( $this->cron_key ) ) {
    1615:       //  wp_schedule_event( time(), $this->cron_recurrance, $this->cron_key );
    1616:       //}
    1617:       /*
    1618:        * If we have existing settings and we are either upgrading or reactivating we
    1619:        * previously had a _credentials element then reauthenticate and record that change.
    1620:        */
    1621:       if ( $this->has_api() ) {
    1622:         $api = $this->get_api();
    1623:         /**
    1624:          * @var RESTian_Auth_Provider_Base $auth_provider
    1625:          */
    1626:         $auth_provider = $api->get_auth_provider();
    1627:         $auth_form = $this->get_auth_form();
    1628:         if ( ! $auth_form )
    1629:           wp_die( __( 'There is no auth form configured. Call $admin_page->set_auth_form( $form_name ) inside initialize_admin_page( $admin_page ).', 'sidecar' ) );
    1630:
    1631:         $account_settings = $auth_form->get_settings();
    1632:         $credentials = $auth_provider->extract_credentials( $account_settings->get_values() );
    1633:         $credentials = array_merge( $auth_provider->get_new_credentials(), $credentials );
    1634:         $credentials = $auth_provider->prepare_credentials( $credentials );
    1635:
    1636:         /**
    1637:          * 'account' contains credentials and grant merged
    1638:          */
    1639:         if ( ! $auth_provider->is_credentials( $credentials ) ) {
    1640:           /**
    1641:            * Allow the auth provider to establish defaults in the grant if needed.
    1642:            * This is an unusual need, but Lexity.com needed it.
    1643:            */
    1644:           $grant = $auth_provider->prepare_grant( $auth_provider->get_new_grant(), $credentials );
    1645:
    1646:         } else {
    1647:           /**
    1648:            * Attempt to authenticate with available credentials
    1649:            */
    1650:           $response = $api->authenticate( $credentials );
    1651:           /**
    1652:            * If authenticated get the updated grant otherwise get an empty grant
    1653:            */
    1654:           $grant = $response->authenticated ? $response->grant : $auth_provider->get_new_grant();
    1655:
    1656:           /**
    1657:            * Allow the auth provider to establish defaults in the grant if needed.
    1658:            * This is an unusual need, but Lexity.com needed it.
    1659:            */
    1660:           $grant = $auth_provider->prepare_grant( $grant, $credentials );
    1661:
    1662:         }
    1663:         /**
    1664:          * Merge credentials and grant back into $settings['account']
    1665:          */
    1666:         $account_settings->set_values( array_merge( $credentials, $grant ) );
    1667:
    1668:       }
    1669:
    1670:       $settings = $this->get_settings();
    1671:       $settings->installed_version = $this->plugin_version;
    1672:       $settings->update_settings();
    1673:
    1674:     }
    1675:   }
    1676:
    1677:   /**
    1678:    *
    1679:    */
    1680:   function _initialize_admin() {
    1681:     if ( ! $this->_admin_initialized ) {
    1682:       if ( ! method_exists( $this, 'initialize_admin' ) ) {
    1683:         trigger_error( __( 'Plugin must define a $plugin->initialize_admin() method..', 'sidecar' ) );
    1684:         exit;
    1685:       }
    1686:       $this->initialize_admin();
    1687:
    1688:       $this->_admin_initialized = true;
    1689:
    1690:       if ( $this->_api )
    1691:         $this->_api->maybe_set_grant( $this->get_grant() );
    1692:
    1693:     }
    1694:   }
    1695:   /**
    1696:    * @return Sidecar_Form
    1697:    */
    1698:   function get_auth_form() {
    1699:     $this->_initialize_admin();
    1700:     $auth_form = false;
    1701:     /**
    1702:      * @var Sidecar_Admin_Page $page
    1703:      */
    1704:     foreach( $this->_admin_pages as $page ) {
    1705:       $this->initialize_admin_page( $page );
    1706:       if ( $test = $page->get_auth_form() ) {
    1707:         $auth_form = $test;
    1708:         break;
    1709:       }
    1710:     }
    1711:     return $auth_form;
    1712:   }
    1713:
    1714:   /**
    1715:    * @param $property_name
    1716:    * @return bool|string
    1717:    */
    1718:   function __get( $property_name ) {
    1719:     $value = false;
    1720:     if ( preg_match( '#^(.*?_(icon|image|photo))_url$#', $property_name, $match ) && $this->has_image( $match[1] ) ) {
    1721:       $value = call_user_func( array( $this, "get_image_url" ), $match[1] );
    1722:     } else if ( preg_match( '#^(.*?)_url$#', $property_name, $match ) && $this->has_url( $match[1] ) ) {
    1723:       /**
    1724:        * Allows magic property syntax for any registered URL
    1725:        * @example: $this->foobar_url calls $this-get_url( 'foobar' )
    1726:        * Enables embedding in a HEREDOC or other doublequoted string
    1727:        * without requiring an intermediate variable.
    1728:        */
    1729:       $value = $this->get_url( $match[1] );
    1730:     } else if ( preg_match( '#^(.*?)_link$#', $property_name, $match ) && $this->has_url( $match[1] ) ) {
    1731:       /**
    1732:        * Same kind of this as _url above.
    1733:        */
    1734:       $url = $this->get_url( $match[1] );
    1735:       $link_text = $this->get_link_text( $match[1] );
    1736:       $class = $this->get_link_class( $match[1] );
    1737:       $value = "<a target=\"_blank\"{$class} href=\"{$url}\">{$link_text}</A>";
    1738:     } else {
    1739:       Sidecar::show_error( 'No property named %s on %s class.', $property_name, get_class( $this ) );
    1740:     }
    1741:     return $value;
    1742:   }
    1743: }
    1744: