<?php
//Models contain the underlying data of each views.

//A model is an object instantiated by the collection class.
//Before instantiation, the collection class has made sure that the model has at least :
//1) a unique id
//2) a hook
//3) a priority set.

//Once alive, a model's job is :
//- instantiate its view. The model must check if its related view has to be instantiated with the default view class or a child of it.
//- assign the view to its rendering hook


if ( ! class_exists( 'CZR_Model' ) ) :
  #[\AllowDynamicProperties]
  class CZR_Model {
    static $instance;

    //the model properties
    //each view will inherit those properties
    public $hook = "";//this is the default hook declared in the index.php template
    public $render = false;
    public $view_instance;
    public $id = "";
    public $model_class = false;
    public $query = false;
    public $priority = 10;
    public $template = "";
    public $element_tag;
    public $element_id;
    public $element_class = "";
    public $element_attributes;
    public $html = "";
    public $callback = "";
    public $cb_params = array();
    public $early_setup = false;
    public $controller = "";
    public $visibility = true;//can be typically overriden by a check on a user option

    public $defaults   = array();

    //on instantiation the id is unique and the priority propery setup
    //=> those treatments have been managed by the collection
    function __construct( $model = array() ) {
          self::$instance =& $this;
          $CZR            = CZR();

          //here is where extension classes can set their preset models
          $_preset = $this -> czr_fn_get_preset_model();

          if ( is_array($_preset) && ! empty( $_preset ) )
            $model = wp_parse_args( $_preset, $model );

          //here is where extension classes can modify the model params before they're parsed
          //becoming model properties
          $model = $this -> czr_fn_extend_params( $model );


          if ( empty( $model ) ) {
            do_action('czr_dev_notice', 'in CZR_MODEL construct : a model has no id ');
            return;
          } elseif ( FALSE == $model['id'] ) {
            //if model ID has been set to false => silent exit. Useful in cases when in czr_fn_extend_params the model
            //itself understands that it has to exit its instantiation
            $CZR -> collection -> czr_fn_delete( $this -> id );
            return;
          }


          //inside will make the equivalent of wp_parse_args() with default model property values
          $this -> czr_fn_update( $model );

          //at this stage the mode must at least have :
          //1) a unique id
          //2) a priority set
          //3) a hook => not anymore since czr_fn_render_template()
          if ( ! $this -> czr_fn_can_model_be_instantiated() ) {
            $CZR -> collection -> czr_fn_delete( $this -> id );
            return;
          }

          //maybe alter body class
          if ( method_exists( $this, 'czr_fn_body_class' ) )
            add_filter( 'body_class', array( $this, 'czr_fn_body_class' ) );

          //maybe add style
          $this -> czr_fn_maybe_add_style();

          //a way to allow models to act on their view
          $this -> czr_fn_add_view_pre_and_post_actions();

          //Allow models to filter their view visibility
          add_filter( "czr_do_render_view_{$this -> id}", array( $this, 'czr_fn_maybe_render_this_model_view' ), 0 );

          //adds the view instance to the model : DO WE REALLY NEED TO DO THAT ?
          //view instance as param
          add_action( "view_instantiated_{$this -> id}"   , array( $this, 'czr_fn_add_view_to_model'), 10, 1 );

          //takes the view instance as param
          add_action( "view_instantiated_{$this -> id}"   , array( $this, 'czr_fn_maybe_hook_or_render_view'), 20, 1 );


          //Maybe instantiate the model's view
          //listens to 'wp' if not fired yet, or fire the instantiation
          if ( ! did_action('wp') )
            add_action( 'wp'                                , array( $this, 'czr_fn_maybe_instantiate_view' ), 999 );
          else
            $this -> czr_fn_maybe_instantiate_view();

    }//construct


    //add this instance to the view description in the collection
    //=> can be used later for deregistration
    //hook : view_instantiated
    function czr_fn_add_view_to_model( $view_instance ) {
          $this -> czr_fn_set_property( 'view_instance', $view_instance );
    }



    /**********************************************************************************
    * INSTANCIATE THE MODEL VIEW => check the controllers,
    * @wp_timezone_override_offset(); check if it's been changed or deleted ?
    ***********************************************************************************/
    //default hook : wp | 1000
    //@return void()
    public function czr_fn_maybe_instantiate_view() {
          do_action( "pre_instantiate_view" );
          $CZR            = CZR();
          //this check has already been done before instantiating the model.
          //Do we really need this again here ?
          if ( ! $CZR -> controllers -> czr_fn_is_possible($this -> czr_fn_get_model_as_array() )  )
            return;

          //instantiate the view with the current model object as param
          $view_instance = new CZR_View( $this );
    }//fn



    /**********************************************************************************
    * ACTIONS ON MODEL INSTANCIATION : MAYBE ADD SPECIFIC MODEL STYLE
    ***********************************************************************************/
    public function czr_fn_maybe_add_style() {
          //for now just add filter to czr_fn_user_options_style
          if ( method_exists( $this, 'czr_fn_user_options_style_cb' ) )
            add_filter( 'czr_user_options_style', array( $this, 'czr_fn_user_options_style_cb' ) );
    }//fn



    /***********************************************************************************
    * ACTIONS ON VIEW READY
    * => THE POSSIBLE VIEW CLASS IS NOW INSTANCIATED
    ***********************************************************************************/
    //hook : 'view_instantiated'
    //@param $instance is the view instance object, can be CZR_View or a child of CZR_View
    //hook the rendering method to the hook
    //$this -> view_instance can be used. It can be a child of this class.
    public function czr_fn_maybe_hook_or_render_view( $instance )  {
          if ( empty( $this -> id ) ) {
              do_action('czr_dev_notice', 'In CZR_Model, a model is missing its id.' );
              return;
          }

          //Are we in czr_fn_render_template case
          //=> Typically yes if did_action('template_redirect'), since every model are registered on 'wp'
          //AND if the render property is forced to true
          //if not check if template_redirect has already been fired, to see if we are in a czr_fn_render case
          if ( did_action( 'template_redirect' ) && $this -> render ) {
              $instance -> czr_fn_maybe_render();
              return;//this is the end, beautiful friend.
          }

          //What are the requested hook and priority ?
          //=> this can be overriden in an extended model for example
          //? using the czr_fn_set_property ( 'hook' , 'value' ) could also be an option in an extended model ?
          $_da_hook     = apply_filters("_da_hook_{$this -> id}" , $this -> hook );
          $_da_priority = apply_filters("_da_priority_{$this -> id}" , $this -> priority );


          //Renders the view on the requested hook
          //'cause yes we do have a hook at this point, who doubts about that ?
          //Well me, but I know I shouldn't. I'm just a freaky damned scary lone coder in the night riding a sparkle horse.
          if ( false == $_da_hook )
            return;

          add_action( $_da_hook, array( $instance , 'czr_fn_maybe_render' ), $_da_priority );
          //emit an event each time a view is hooked
          do_action( 'view_hooked' , $this -> id );
    }


    //@param instance view
    //@return void()
    public function czr_fn_unhook_view() {
          if ( false == $this -> hook || ! is_object( $this -> view_instance) )
            return;
          remove_action( $this -> hook, array( $instance , 'czr_fn_maybe_render' ), $this -> priority );
          //say it
          do_action( 'view_unhooked' , $this -> id );
    }


    /***********************************************************************************
    * EXPOSED GETTERS / SETTERS
    ***********************************************************************************/
    //normalizes the way we can access and change a single model property
    //=> emit an event to update the collection
    //@return void()
    public function czr_fn_set_property( $property, $value ) {
          $this -> $property = $value;

          //will trigger a collection update
          //pass : id, (object) model, changed property, new property value
          do_action( 'model_property_changed', $this -> id, $this , $property, $value );
    }

    //normalizes the way we can access and change a single model property
    //@return the property
    public function czr_fn_get_property( $property, $args = array() ) {
          if ( method_exists( $this, "czr_fn_get_{$property}" ) ) {
              return call_user_func_array( array($this, "czr_fn_get_{$property}"), $args );
          }
          return isset ( $this -> $property ) ? $this -> $property : '';
    }

    //@returns the model property as an array of params
    public function czr_fn_get_model_as_array() {
          $model = array();
          foreach ( array_keys( get_object_vars( $this ) ) as $key ) {
            $model[ $key ] = $this->$key;
          }
          return $model;
    }

    //@return array()
    //Extension models can use this to declare a preset model
    //is fired on instantiation
    //@return array()
    protected function czr_fn_get_preset_model() {
      return array();
    }

    //@return array()
    //Extension models can use this to update the model params with a set of new ones
    //is fired on instantiation
    //@param = array()
    protected function czr_fn_extend_params( $model = array() ) {
          //parse args into the model
          if ( ! empty( $model[ 'args' ] ) && is_array( $model[ 'args' ] ) ) {
            $model = wp_parse_args( $model[ 'args' ], $model );
            unset( $model[ 'args' ] );
          }
          return $model;
    }

    //@return void()
    //update the model properties with a set of new ones
    //is fired on instantiation
    //@param = array()
    public function czr_fn_update( $model = array(), $merge_defaults = true ) {
          /*
          * Parse model into $this->defaults, if not empty
          * This property will be merged with the array of properties
          * It allows us, when updating a model, to specify only those properties that need to be different from the defaults.
          * Specially interesting for those "singleton" models, whose only one instance is used throughout the page.
          * The fact that the models retain the properties could cause undesired effects.
          * E.g.
          * - Edit Button
          * the edit button model, which feeds the template,
          * is filled by the slider of posts with a text saying "Customize or remove the posts slider",
          * this means that if we render the edit button in a list of posts below the slider
          * we are forced to specify the new text, which, in most of the cases would be just "Edit" (defaults)
          */
          if ( $merge_defaults && ! empty( $this -> defaults ) ) {
                $model = wp_parse_args( $model, $this->defaults );
          }

          foreach ( $model as $key => $value ) {
               if ( ! isset( $this->key) || ( isset( $this->$key ) && $model[ $key ] != $this->$key ) )
                      $this->$key = $model[ $key ];
          }

          //emit an event when a model is updated
          do_action( 'model_updated', $this -> id );
    }

    /*
    * Reset the defaults properties to their original values
    */
    public function czr_fn_reset_to_defaults () {
          $this -> czr_fn_update( $this->defaults );
    }



    /***********************************************************************************
    * ACTIONS ON VIEW READY
    * => THE POSSIBLE VIEW CLASS IS NOW INSTANCIATED
    ***********************************************************************************/
    //@hook czr_do_render_view_{$this -> id}
    //@return bool
    // Controls the rendering of the model's view
    public function czr_fn_maybe_render_this_model_view() {
          return $this -> visibility;
    }

    // Maybe add pre_rendering_view action hook callback to filter the model before rendering
    // Extended classes might want to override this method, so to hook them to a specific pre rendering id
    // I prefer to not allow the automatic hooking to a specific view without checking the existence of a callback to avoid the useless adding of a "dummy" cb ot the array of action callbacks
    // Though makes sense to hook a certain model ID to its view pre_rendering to parse its own properties before rendering. Example:
    // The class parameter will be stored in the model as an array to allow a better way to filter it ( e.g. avoid duplications), but to make it suitable for the rendering, it must be transformed in a string
    // Maybe we can think about make the model attributes, when needed, a set of
    // value, "sanitize" callback and let the view class do this..
    protected function czr_fn_add_view_pre_and_post_actions() {

          //by default filter this module before rendering (for default properties parsing, e.g. element_class )
          add_action( "pre_rendering_view_{$this -> id}", array($this, "czr_fn_pre_rendering_my_view_cb" ), 9999 );

          //by default filter this module before rendering (for default properties parsing, e.g. element_class )
          add_action( "post_rendering_view_{$this -> id}", array($this, "czr_fn_post_rendering_my_view_cb" ), 9999 );
    }


    /*
    * Before rendering this model view allow the setup of the late properties (e.g. in the loops)
    * Always sanitize those properties for being printed ( e.g. array to string )
    */
    public function czr_fn_pre_rendering_my_view_cb( $model ) {
          if ( method_exists( $this, 'czr_fn_setup_late_properties' ) )
            $this -> czr_fn_setup_late_properties();

          $this -> czr_fn_sanitize_model_properties( $model );
    }

    /*
    * After rendering this model view allow the reset (e.g. in the loops)
    */
    public function czr_fn_post_rendering_my_view_cb( $model ) {
          if ( method_exists( $this, 'czr_fn_reset_late_properties' ) )
            $this -> czr_fn_reset_late_properties();

    }

    protected function czr_fn_sanitize_model_properties( $model ) {
          $this -> element_class  = $this -> czr_fn_stringify_model_property( 'element_class' );
    }


    /**********************************************************************************
    * HELPERS
    ***********************************************************************************/
    protected function czr_fn_stringify_model_property( $property ) {
          if ( isset( $this -> $property ) )
           return czr_fn_stringify_array( $this -> $property );
          return '';
    }


    //@return bool
    //at this stage the mode must at least have :
    //1) a unique id
    //2) a priority set
    private function czr_fn_can_model_be_instantiated() {
      //the model must be an array of params
      //the hook is the only mandatory param => not anymore since czr_fn_render_template()
      if ( ! is_numeric( $this -> priority ) || empty($this -> id) ) {
        do_action('czr_dev_notice', "In CZR_Model class, a model instantiation aborted. Model is not ready for the collection, it won't be registered. at this stage, the model must have an id, a hook and a numeric priority." );
        return;
      }
      return true;
    }

    //checks if the model exists and is an instance
    //@return bool
    public function czr_fn_has_instantiated_view() {
          return is_object( $this -> view_instance );
    }

  }//end of class
endif;

?><?php
//This is the class managing the collection of models. Here's what it does :
//- registers and de-registers models
//- ensures that each model has a unique id and is well formed
//- Instantiates the relevant models according to their controllers (for models registered on or after 'wp')
//- Instantiates the specified model extended class if any
//- Handles the model's modifications, including deletion
//- Make sure the collection is a public array of model's instance
if ( ! class_exists( 'CZR_Collection' ) ) :
  class CZR_Collection {
    static $instance;
    //public $group = "";//header,content,footer,modules
    //private $args = array();//will store the updated args on model creation and use them to instantiate the child
    public static $pre_registered = array();//will store the models before they are actually checked, instantiated and registered => before 'wp'
    public static $collection = array();//will store all registered model instances
    public static $_delete_candidates = array();//will store deletion of models not added yet

    function __construct( $args = array() ) {
      self::$instance =& $this;
      //listens to filter 'czr_prepare_model', takes 1 param : raw model array()
      //makes sure the model has a unique $id set and a proper priority for its rendereing hook
      //model as param
      add_filter( 'czr_prepare_model'            , array( $this, 'czr_fn_set_model_base_properties'), 10, 1 );

      //if 'wp' has not been fired yet, we will pre-register this model for later instantiation
      //2 params :
      //1) model id
      //2) model instance
      add_action ('pre_register_model'          , array( $this, 'czr_fn_pre_register_model'), 10, 2 );

      //a model_alive event is emitted each time a model object has been properly instantiated and setup
      //=> update the collection by registering the model
      //Takes 2 params
      //1) model id
      //2) model instance
      add_action( 'model_alive'                 , array( $this, 'czr_fn_update_collection' ), 10, 2 );

      //on 'wp', the pre_registered (if any) are registered
      add_action( 'wp'                          , array($this, 'czr_fn_register_pre_registered') );

      //Reacts on 'czr_delete' event
      //1 param = model id
      add_action( 'czr_delete'                   , array( $this, 'czr_fn_delete' ), 10, 1 );

      //listens to a model changed => update the model collection
      //model_property_changed takes two params :
      //model id
      //model params
      add_action( 'model_property_changed'      , array( $this, 'czr_fn_update_collection' ), 10, 2 );

      //listens to a registered change applied to a model => remove it from the register changes list
      //takes one param : model id
      add_action( 'registered_changed_applied'  , array( $this, 'czr_fn_deregister_change' ), 10, 1 );

      //reacts when a model has been deregistered from the collection
      //=> fire czr_fn_delete()
      //=> take the model id as param
      add_action( 'model_deregistered'          , array( $this , 'czr_fn_delete'), 10, 1 );
    }




    /**********************************************************************************
    * REGISTER A MODEL TO THE COLLECTION
    ***********************************************************************************/

    public function czr_fn_register( $model = array() ) {
      $_model_params_array = $model;

      //the first check on the raw model provided
      //=> if the model has been pre-registered and has an id, we also have to check here if it's registered for deletion
      if ( ! $this -> czr_fn_is_model_eligible( $model ) )
        return;

      //this pre-setup will ensure :
      //- the hook is there
      //- the id unicity
      //- the hook priority setup
      //It also makes sure that the registered changes will be applied
      $model = apply_filters( 'czr_prepare_model' , $model );

      //make sure the provided model has at least a hook property set
      //the model must be an array of params
      //the hook is the only mandatory param
      //the id is optional => will be set unique on model setup
      //if 'wp' has not been fired yet, we will pre-register this model for later registration
      ////Once the model is eligible and properly prepared (unique id), let's see if we can
      //1) register it,
      //2) pre-register it,
      //3) or simply abort registration
      if ( ! $this -> czr_fn_can_register_model( $model ) )
        return;

      //INSTANTIATE THE MODEL
      // => the model object will instantiate the view
      // => if already  did_action( 'template_redirect' ) and render == true, the view will be rendered
      //Instantiates the model object : at this stage the 'wp' hook has been fired and we're ready to instantiate the (maybe pre-registered) model
      //at this stage, the model is an array and :
      //- has an id
      //- has a priority
      //- is at least assigned to a hook
      //- we've checked if it was registered for deletion
      //=> let's instantiate
      $_model_id = isset( $model['id'] ) ? $model['id'] : 'undefined';

      $model = $this -> czr_fn_instantiate_model( $model );

      //Silent aborting for those models which "decided" in their constructor they're not allowed to be registered
      if ( isset( $model -> id ) && $this -> czr_fn_has_registered_deletion( $model -> id ) )
        return;

      //abort if the model has not been instantiated
      if ( ! is_object( $model ) ) {
        $_model_id = isset( $model -> id ) ? $model -> id : 'undefined';
        do_action('czr_dev_notice', "The model ( " . $_model_id . ") was not instantiated and could not be registered into the collection." );
        return;
      }

      //REGISTER THE MODEL INTO THE COLLECTION
      //At this stage, the model is an instanciated object
      //=> an event is emitted : this will trigger the collection update => the model will be registered in the collection
      do_action( 'model_alive' , $_model_id, $model );

      //specific event for this model.
      do_action( "{$_model_id}_model_alive", $_model_id, $model );


      if ( $this -> czr_fn_is_registered( $model -> id ) ) {
        //emit an event on model registered
        //can be used with did_action() afterwards
        do_action( "model_registered_{$model -> id}" );
      } elseif ( ! empty( $model -> id ) ) {
        do_action('czr_dev_notice', "A model instance ( " . $model -> id . ") was not registered into the collection." );
        return;
      } else //silent exit ( for those models whose instatiation has been stopped in the constructor as result of the model business logic )
        return;
      return $model -> id;
    }







    /**********************************************************************************
    * BEFORE REGISTRATION
    ***********************************************************************************/
    //hook : 'czr_can_use_model'
    //Check if the model is registered for deletion first
    //the model must be an array of params
    //the hook is the only mandatory param => not anymore since czr_fn_render_template
    //the id is optional => will be set unique on model instantiation
    public function czr_fn_is_model_eligible( $model = array() ) {
      //is model registered for deletion ?
      if ( isset( $model['id'] ) && $this -> czr_fn_has_registered_deletion( $model['id'] ) )
        return;

      if ( ! is_array($model) || empty($model) ) {
        do_action('czr_dev_notice', "CZR_collection : A model is not eligible for the collection, it won't be registered. The model must be an array of params." );
        return;
      }
      return true;
    }



    //at this stage, the model has a hook but the id unicity, and the priority have not been checked yet
    //=> we need to make sure the model has a unique $id and a proper priority for its rendering hook
    //hook filter 'czr_prepare_model' in czr_fn_register
    //@param model array
    //@return model array() updated
    public function czr_fn_set_model_base_properties( $model = array() ) {
      $id       = isset($model['id']) ? $model['id'] : "";
      $priority = isset($model['priority']) ? $model['priority'] : "";
      $template = isset($model['template']) ? $model['template']  : "";//the template name can be used to define the id

      if ( isset($model['hook']) && ! empty($model['hook']) && false != $model['hook'] ) {
        //makes sure we assign a unique ascending priority if not set
        $model['priority']  = $this -> czr_fn_set_priority( $model['hook'] , $priority );
      } else {
        $model['priority'] = 10;
        $model['hook'] = "";
      }
      //check or set the name unicity
      $model['id']        = $this -> czr_fn_set_unique_id( $id , $model['hook'], $model['priority'], $template );

      //don't go further if we still have no id set
      if ( ! $model['id'] ) {
        do_action('czr_dev_notice', "A model has no id set." );
        return;
      }

      //at this stage the priority is set and the id is unique
      //a model with a unique id can be registered only once
      //a model with a promise registered deletion won't be registered
      if ( $this -> czr_fn_is_registered( $model['id'] ) ) {
        do_action('czr_dev_notice', "CZR_Collection. Model : ". $model['id'] ." . The id is still not unique. Not registered." );
        return;
      }
      return $model;
    }


    //at this point, the raw model has had a first setup to ensure id
    //@return boolean
    //@param array() raw model
    private function czr_fn_can_register_model( $model = array() ) {
      $bool = false;
      $CZR  = CZR();
      //the first check is on the visibility
      //Typically : Has the user allowed this model's view in options ?
      if ( isset( $model['visibility']) && ! (bool) $model['visibility'] )
        $bool = false;

      //if the model has early hooks (before wp) , typically a pre_get_post action for example
      // => the the model has to be instantiated
      if ( isset($model['early_setup']) && ! empty($model['early_setup']) )
        $bool = true;

      //if 'wp' has not been fired yet, we will pre-register this model for later registration
      if ( ! did_action('wp') ) {
        //we will use this event to fire the pre-registration
        do_action( 'pre_register_model', $model['id'], $model );
        $bool = false;
      }
      //if 'wp' has been fired (or is currently being fired) 1) check the controller if set
      else {
        $bool = $CZR -> controllers -> czr_fn_is_possible( $model );
      }
      return apply_filters('czr_can_register_model', $bool, $model );
    }



    /**********************************************************************************
    * PRE-REGISTRATION
    ***********************************************************************************/
    //update the pre_register static property
    //hook : pre_register_model
    //@return void()
    //@param id string
    //@param model array
    function czr_fn_pre_register_model( $id, $model = array() ) {
      $pre_registered = self::$pre_registered;
      //is this model already pre_registered ?
      //=> if yes, it can't be registered again. However it should be accessible with a change action.
      if ( isset($pre_registered[$id]) ) {
        do_action('czr_dev_notice', "Model " . $id . " has already been pre-registered." );
        return;
      }

      $pre_registered[$id] = $model;
      self::$pre_registered = $pre_registered;
    }


    //@return void()
    //=> removes a pre_register model from the pre_registered list
    function czr_fn_remove_pre_registered($id) {
      $pre_registered = self::$pre_registered;
      if ( isset($pre_registered[$id]) )
        unset($pre_registered[$id]);
      self::$pre_registered = $pre_registered;
    }


    //registers the pre-registered model when query is ready
    //hook : wp
    //@return void()
    function czr_fn_register_pre_registered() {
      foreach ( self::$pre_registered as $id => $model ) {
        //removes from the pre_registered list
        $this -> czr_fn_remove_pre_registered($id);
        //registers
        $this -> czr_fn_register($model);
      }
      //say it to the api
      do_action( 'pre_registered_registered', self::$pre_registered );
    }





    /**********************************************************************************
    * INSTANCIATE THE MODEL'S CLASS
    ***********************************************************************************/
    //some model's need to be augmented by an extended class.
    //hook : 'wp'
    //this method load the relevant model class file and return the instance
    //@return instance object
    public function czr_fn_instantiate_model($model) {
      $instance = null;

      //try to instantiate the model specified in the model_class param
      //if not found try to retrieve it from the template param (mandatory):
      //a) The model_class, when specified, must refer to a valid model otherwise a notice will be fired.
      //b) Also if a whatever model has been instantiated it must be a subclass of CZR_Model - otherwise a notice will be fired.
      //c) Else If no suitable model has been instantiated instantiate the base model class
      foreach ( array( 'model_class', 'template' ) as $_model_class ) {
        if ( ! isset($model[ $_model_class ]) || empty($model[ $_model_class ]) )
            continue;

        //A model class has been defined, let's try to load it and instantiate it
        //The model_class arg can also be an array in the form array( 'parent' => parent_model_class (string), 'name' => model_class ('string') )
        if ( 'model_class' == $_model_class && is_array( $model[ 'model_class' ] ) && array_key_exists( 'name', $model['model_class'] ) ) {

          if ( ! class_exists( sprintf( 'CZR_%s_model_class', $model['model_class']['parent'] ) ) ) {
            $this -> czr_fn_require_model_class( $model['model_class']['parent'] );
          }

          $model_class     = $model[ $_model_class ]['name'];

        } else {
          $model_class     = $model[ $_model_class ];
        }


        $model_class_name     = sprintf( 'CZR_%s_model_class', basename( $model_class ) );

        if ( ! class_exists($model_class_name) ) {
          $this -> czr_fn_require_model_class( $model_class );
        }


        if ( class_exists($model_class_name) ) {
          $instance = new $model_class_name( $model );
        }

        if ( ! is_object($instance) && 'model_class' == $_model_class ) {
          do_action('czr_dev_notice', "Model : " . $model['id'] . ". The model has not been instantiated." );
          return;
        }
        //A model must be CZR_model or a child class of CZR_model.
        if ( is_object($instance) && ! is_subclass_of($instance, 'CZR_Model') ) {
          do_action('czr_dev_notice', "Model : " . $model['id'] . ". View Instantiation aborted : the specified model class must be a child of CZR_Model." );
          return;
        } else break;
      }//end foreach

      if ( ! is_object( $instance ) )
        return new CZR_Model( $model );

      return $instance;
    }


    //This method require the requested class file and returns a boolean state of this action
    //the boolean is required. If false, the theme will fallback on the CZR_Model base class
    private function czr_fn_require_model_class( $_model_class ) {
        $model_class_basename = basename( $_model_class );
        $model_class_dirname  = dirname( $_model_class );
        $model_path           = sprintf( 'models/%1$s/class-model-%2$s.php', $model_class_dirname, $model_class_basename );

        //this filter is NOT used in the customizr theme
        //=> its intended to be used in plugins / addons, like pro grid for ex.
        $path = apply_filters(
            "czr_model_class_path",
            false,
            $model_class_basename,
            $model_path
        );

        //If not filtered ( normal case in customizr ), use the default path prefix + model path
        if ( ! $path || ! file_exists( $path ) ) {
          return  CZR() -> czr_fn_require_once(
              CZR_PHP_FRONT_PATH . $model_path
          );
        }

        return require_once( $path );
    }








    /**********************************************************************************
    * UPDATE COLLECION
    ***********************************************************************************/
    //hook : 'model_alive' and 'model_property_changed'
    //The job of this method is :
    //1) to add a model to the collection
    //2) or to update an existing model
    //
    //=> always update the model list before rendering something
    //=> a model might have been registered in the delete / change candidates
    //=> this is fired on model_property_changed event
    //=> when a single model property has been changed in CZR_Model::czr_fn_set_property()
    //@param id string
    //@param $model instance object
    public function czr_fn_update_collection( $id, $model ) {
      if ( ! $id || ! is_object($model) )
        return;

      //Check if we have to run a registered deletion here
      if ( $this -> czr_fn_is_registered( $id ) && $this -> czr_fn_has_registered_deletion( $id ) ) {
        do_action( 'czr_delete' , $id );
        return;
      }

      //Adds or updates a specific model in the collection
      $collection = self::$collection;
      $collection[$id] = $model;
      self::$collection = $collection;

      //emit an event on each collection updates
      do_action( 'collection_updated', $id, $model );
    }




    /**********************************************************************************
    * DE-REGISTER AN INSTANCIATED MODEL
    ***********************************************************************************/
    //keep in mind that the instance of the previous model with initial argument will still exists...
    //so will the additional class instance if any
    //@todo shall we store all models instances and delete them when requested ?
    public function czr_fn_deregister( $id, $model ) {
      if ( ! is_array($model) )
        return;

      //Removes the previously set action
      if ( ! is_object($model['view_instance']) )
        do_action('czr_dev_notice', 'Attempt to de-register, but no model instance for id : '. $id );
      else if ( ! empty( $model['hook'] ) )
        remove_action( $model['hook'], array( $model['view_instance'], 'czr_fn_maybe_render'), $model['priority'] );

      //Emit an event on model deregistered
      //=> will trigger the model delete action from collection
      do_action( 'model_deregistered' , $id );
    }





    /**********************************************************************************
    * DELETE A MODEL FROM THE COLLECTION
    ***********************************************************************************/
    //the model might not have been created yet
    //=> register a promise deletion in this case
    //IMPORTANT : always use the CZR_Collection::$instance -> _models property to access the model list here
    //=> because it can be accessed from a child class
    public function czr_fn_delete( $id = null ) {
      if ( is_null($id) )
        return;

      $collection = self::$collection;
      if ( isset($collection[$id]) ) {
        unset($collection[$id]);
        self::$collection = $collection;
        //may be remove from the deletion list
        $this -> czr_fn_deregister_deletion($id);
        //Emit an event on model deleted
        do_action( 'model_deleted' , $id );
      }
      else
        $this -> czr_fn_register_deletion( $id );
      return;
    }


    private function czr_fn_deregister_deletion($id) {
      $to_delete = self::$_delete_candidates;
      if ( $this -> czr_fn_has_registered_deletion($id) )
        unset($to_delete[$id]);
      self::$_delete_candidates = $to_delete;
    }


    private function czr_fn_register_deletion($id) {
      $to_delete = self::$_delete_candidates;
      //avoid if already registered for deletion
      if ( $this -> czr_fn_has_registered_deletion($id) )
        return;

      $to_delete[$id] = $id;
      self::$_delete_candidates =  $to_delete;
    }


    private function czr_fn_has_registered_deletion($id) {
      return array_key_exists( $id, self::$_delete_candidates );
    }




    /**********************************************************************************
    * GETTERS / SETTERS
    ***********************************************************************************/
    //@return a single model set of params array
    public function czr_fn_get_model( $id = null ) {
      $collection = self::$collection;
      if ( ! is_null($id) && isset($collection[$id]) )
        return (array)$collection[$id];
      return array();
    }

    //@return model instance or false
    //@param model id string
    public function czr_fn_get_model_instance( $id = null ) {
      if ( is_null($id) )
        return;

      $collection = self::$collection;
      if ( ! isset($collection[$id]) )
        return;
      return $collection[$id];
    }


    //@return the collection of models
    public function czr_fn_get_collection() {
      //uses self::$instance instead of this to always use the parent instance
      return self::$collection;
    }


    /**********************************************************************************
    * HELPERS
    ***********************************************************************************/
    //@return bool
    private function czr_fn_is_pre_registered($id) {
      return array_key_exists($id, self::$pre_registered);
    }


    //this function recursively :
    //1) checks if the requested priority is available on the specified hook
    //2) set a new priority until until it's available
    private function czr_fn_set_priority( $hook, $priority ) {
      $priority = empty($priority) ? 10 : (int)$priority;
      $available = true;
      //loop on the existing model object in the collection
      foreach ( $this -> czr_fn_get_collection() as $id => $model) {
        if ( ! isset($model -> hook) )
          continue;
        if ( $hook != $model -> hook )
          continue;
        if ( $hook == $model -> hook && $priority != $model -> priority )
          continue;
        $available = false;
      }
      return $available ? $priority : $this -> czr_fn_set_priority( $hook, $priority + 1 );
    }


    //Recursively create a unique id when needed
    //@return string id
    private function czr_fn_set_unique_id( $id, $hook, $priority, $template ) {
      //if id not set, then :
      //1) try to create a unique id from the template name if specified
      //2) otherwise create a unique id from hook_priority
      if ( empty($id) || is_null($id) ) {
        if ( ! empty($template) )
          $id = basename( $template );
        else
          $id = "{$hook}_{$priority}";
      }


      //return it now if available
      if ( ! $this -> czr_fn_is_registered($id) && ! $this -> czr_fn_is_pre_registered($id) )
        return apply_filters('czr_set_model_unique_id' , $id, $hook, $priority );

      //add hyphen add the end if not there
      $id                 = ! is_numeric(substr($id, -1)) ? $id . '_0' : $id;
      $id_exploded        = explode('_' , $id);
      $_index             = end($id_exploded);
      $_key               = key($id_exploded);
      //set new value
      $id_exploded[$_key] = $_index + 1;
      $id                 = implode( "_" , $id_exploded );

      //recursive check
      return $this -> czr_fn_set_unique_id( $id, $hook, $priority, $template );
    }



    //checks if a model exists in the collection
    //@param string id
    //@return bool
    public function czr_fn_is_registered( $id ) {
      return array_key_exists( $id, self::$collection );
    }

  }//end of class
endif;

?><?php
//This is the view class. Each front end part of the Customizr theme is rendered through a view instance.
//Each view inherits its properties from a model instance.
//The model decides if it has to instantiate the view or not, according to the context

//Once properly instantiated with the model as parameter, a view does not think to anything else than rendering what and where (hook) we ask it to.
//This class jobs are :
//- render either html, WordPress template or more complex content, depending on the its model settings.
//- when renders on a template, the view must pass its model to the WordPress template through the $wp_query global


if ( ! class_exists( 'CZR_View' ) ) :
  class CZR_View {
    public $model;
    function __construct( $model ) {
          // $keys = array_keys( get_object_vars( $this ) );

          // foreach ( $keys as $key ) {
          //   if ( isset( $model[ $key ] ) ) {
          //     $this->$key = $model[ $key ];
          //   }
          // }
          $this -> model = $model;

          //emit event on view instantiation
          //Will be listen to by the model and trigger the maybe_hook_view callback
          do_action( "view_instantiated_{$this -> model -> id}", $this );
    }




    /**********************************************************************************
    * RENDERS
    ***********************************************************************************/
    //hook : $model['hook']
    //NOTE : the $this here can be the child class $this.
    //
    //This method can fired or scheduled :
    //1) in function czr_fn_render_template( $template, $args = array() ) for an already registered model
    //  => Example : header
    //2) in CZR_Model::czr_fn_maybe_hook_or_render_view, FIRED if did_action('template_redirect') and the 'render' model property is true
    //3) else in CZR_Model::czr_fn_maybe_hook_or_render_view, SCHEDULED ( hooked )
    public function czr_fn_maybe_render() {
        //this event is used to check for late deletion or change before actually rendering
        //will fire czr_fn_apply_registered_changes_to_instance
        //do_action( 'pre_render_view', $this -> id );

        if ( ! apply_filters( "czr_do_render_view_{$this -> model -> id}", true, $this->model ) )
          return;

        //allow filtering of the model before rendering (the view's model is passed by reference)
        //THIS IS WHERE THE czr_fn_setup_late_properties is fired
        // With this action in the base model class constructor : add_action( "pre_rendering_view_{$this -> id}", array($this, "czr_fn_pre_rendering_my_view_cb" ), 9999 );
        //WHAT DOES SETUP LATE PROPERTIES?
        // => example for the loop model, it setup the template to render inside the loop, which are defined as model properties.
        //  public $loop_item_template
        do_action_ref_array( 'pre_rendering_view', array(&$this -> model) );
        do_action_ref_array( "pre_rendering_view_{$this -> model -> id}", array(&$this -> model) );

        //re-check visibility
        if ( ! apply_filters( "czr_do_render_view_{$this -> model -> id}", true, $this->model ) )
          return;

        //do_action( "__before_{$this -> model -> id}" ); <= DO WE REALLY NEED THOSE ?

        //ADD ATTRIBUTES TO THE WRAPPER
        if ( is_user_logged_in() ) {
            /* Maybe merge debug info into the model element attributes */
            if($this -> model -> element_attributes !== null){
              $this -> model -> element_attributes = is_array( $this -> model -> element_attributes ) ? $this -> model -> element_attributes : explode( ' ', $this -> model -> element_attributes );
              $this -> model -> element_attributes = join( ' ', array_filter( array_unique( array_merge( $this -> model -> element_attributes, array(
                  'data-czr-model_id="'. $this -> model -> id .'"',
                  isset( $this -> model -> template ) ? 'data-czr-template="templates/parts/'. $this -> model -> template .'"' : ''
              )))) );
            }
            
        }

        $this -> czr_fn_render();


        // (the view's model is passed by reference)
        do_action_ref_array( 'post_rendering_view', array(&$this -> model) );
        do_action_ref_array( "post_rendering_view_{$this -> model -> id}", array(&$this -> model) );
    }



    //might be overriden in the child view if any
    public function czr_fn_render() {
        if ( ! empty( $this -> model -> html ) )
            echo $this -> model -> html;
        //NOTE : a model -> template can be 'my_template' or 'content/post-lists/my_template'
        //=> if we want to use the template property as a suffix for filtering, we need to sanitize it
        if ( ! empty( $this -> model -> template ) ) {
            $template_path = trailingslashit( apply_filters( 'czr_template_path', "templates/parts/", $this -> model -> id, $this -> model ) );
            $_template_file_path = apply_filters( 'czr_template_file_path', false, $this -> model -> template );

            if ( false === $_template_file_path || ! file_exists( $_template_file_path ) ) {
                //get the filename
                $_template_file_path = czr_fn_get_theme_file_path( "{$template_path}{$this -> model -> template}.php" );
            }

            if ( false !== $_template_file_path ) {
                czr_fn_set_current_model( $this -> model );

                ob_start();
                  load_template( $_template_file_path, $require_once = false );
                  $_temp_content = ob_get_contents();
                ob_end_clean();
                if ( ! empty( $_temp_content ) )
                  echo $_temp_content;

                czr_fn_reset_current_model();
            }
        }

        if ( ! empty( $this -> model -> callback ) )
            czr_fn_fire_cb( $this -> model -> callback, $this -> model -> cb_params );
    }



    //at this stage, the view is instantiated
    //@return void()
    private function czr_fn_update_model_instance( $id, $new_params ) {
          //get current params
          $current_params = $this -> czr_fn_get_view($id);
          if ( ! $current_params || ! is_array($current_params) )
            return;
          //pre-process new params
          $new_params = wp_parse_args( $new_params, $current_params );

          //update the modified view properties
          //=> will automatically trigger the collection update
          foreach ($new_params as $property => $value) {
            if ( $value != $current_params[$property] )
              $current_params['view_instance'] -> czr_fn_set_property( $property, $value );
          }
    }


    /**********************************************************************************
    * PUBLIC HELPERS
    ***********************************************************************************/
    public function czr_fn_get_instance() {
          return $this;
    }

  }//class
endif;

?><?php
//the controllers are organized by groups in 4 classes
//Header, Content, Footer, Modules.
//The controller group classes are instantiated on demand if any of a view (or its child) requested it
//If the model is used as a method parameter, it shall be an array()
//=> because a controller can be checked and fired before the model has been instantiated
//=> always check the existence of the id
if ( ! class_exists( 'CZR_controllers' ) ) :
  class CZR_controllers {
    static $instance;
    private $controllers;
    static $controllers_instances = array();

    function __construct( $args = array() ) {
          self::$instance =& $this;
          $this -> controllers = array(
            'header' => array(

              'head',

              'topbar_wrapper',

              'title_alone',
              'title_next_logo',
              'logo_wrapper',
              'logo',

              'topbar_tagline',
              'branding_tagline_aside',
              'branding_tagline_below',
              'branding_tagline',
              'mobile_tagline',


              'navbar_primary_menu',
              'navbar_secondary_menu',
              'sidenav',
              'topbar_menu',
              'mobile_menu',

              'menu_button',
              'mobile_menu_button',

              'sidenav_menu_button',
              'sidenav_navbar_menu_button',

              'topbar_social_block',

              'desktop_topbar_search',
              'desktop_primary_search',
              'mobile_navbar_search',
              'mobile_menu_search',

              'desktop_topbar_wc_cart',
              'desktop_primary_wc_cart',
              'mobile_wc_cart',

              'primary_nav_utils',
              'topbar_nav_utils'
            ),

            'content' => array(
              'post_list',
              'post_list_masonry',
              'post_list_plain',
              'post_list_plain_excerpt',
              'right_sidebar',
              'left_sidebar',
              'single_author_info',
              'comment_list',
              'comments',
              'post',
              'posts_navigation',

              'regular_page_heading',
              'regular_post_heading',
              'regular_attachment_image_heading',

              'attachment_image',

              'archive_heading',
              'author_description',
              'posts_list_description',
              'search_heading',
              'post_heading',
              'lefts_social_block',
              'rights_social_block',
              'post_metas'
            ),
            'footer' => array(
              'btt_arrow',
              'footer_push',
              'footer_horizontal_widgets',
              'footer_widgets',
              'colophon',
              'footer_social_block'
            ),
            'modules' => array(
              'breadcrumb',
              'social_block',
              'post_list_grid',
              'main_slider',
              'main_posts_slider',
              'featured_pages',
              'search_full_page',
              'social_share',
              'author_socials',
              'related_posts'
            ),
          );

          //Store a group controller instance for later uses
          //takes 2 params
          //group name (string)
          //group controller instance (object)
          add_action( 'group_controller_instantiated', array( $this, 'czr_fn_store_controller_instance'),10, 2);
    }//__construct()




    /***********************************************************************************
    * EXPOSED API
    ***********************************************************************************/
    //1) checks if a controller has been specified for the view. It can be either a function, or the method of a class
    //
    //2) if nothing is specified for the view, then checks if the view controller belongs to a particular group
    //if a match is found, then the group controller class is instantiated if not yet
    //then the existence of the controller method is checked and fired if exists
    //
    //3) if no match is found, the view is not allowed
    //@ $model can be
    //  1) a model object,
    //  2) a model array (not instanciated),
    //  3) a string id of the model
    public function czr_fn_is_possible( $model ) {
          //if the specified param is an id, then get the model from the collection
          if ( is_string($model) )
            $model = array('id' => $model );

          //abort with true if still no model at this stage.
          if ( empty($model) )
            return true;

          //the returned value can be a string or an array( instance, method)
          $controller_cb = $this -> czr_fn_get_controller( $model );
          //FOR TEST ONLY
          //return true;

          if ( ! empty( $controller_cb ) ) {
            return apply_filters( 'czr_fn_set_control' , (bool) czr_fn_return_cb_result( $controller_cb, $model ) );
          }
          return true;
    }



    //@return bool
    //@param array() or object() model
    public function czr_fn_has_controller( $model = array() ) {
          $controller = $this -> czr_fn_build_controller( $model );
          return ! empty( $controller );
    }



    //@return a function string or an array( instance, method )
    //@param array() or object() model
    private function czr_fn_get_controller( $model = array() ) {
          $model = is_object($model) ? (array)$model : $model;
          $controller_cb = "";

          //IS A CONTROLLER SPECIFIED AS PROPERTY OF THIS MODEL ?
          //and is a valid callback
          if ( isset($model['controller']) && is_callable( $model['controller'] )) {
            //a callback can be function or a method of a class
            $controller_cb =  $model['controller'];
          }
          //IS THERE A PRE-DEFINED CONTROLLER FOR THE VIEW ?
          else {
            //the default controller match can either be:
            //a) based on the model id (has the precedence )
            //b) based on the controller model field, when not a callback
            $controller_ids   = array_filter( array(
                $model['id'],
                ! empty( $model['controller'] ) ? $model['controller'] : '',
              )
            );
            if ( $this -> czr_fn_has_default_controller( $controller_ids ) ) {
              $controller_cb = $this -> czr_fn_get_default_controller( $controller_ids );
              //make sure the default controller is well formed
              //the default controller should look like array( instance, method )
              if ( empty($controller_cb) ) {
                do_action( 'czr_dev_notice', 'View : '.$model['id'].'. The default group controller has not been instantiated');
                return "";
              }//if
            }//if has default controller
          }//else
          return $controller_cb;
    }



    //@return boolean
    //@walks the controllers setup array until a match is found
    private function czr_fn_has_default_controller( $controller_ids ) {
          foreach ( $this -> controllers as $group => $views_id ) {
            foreach( $controller_ids as $id ) {
              if ( in_array($id, $views_id) ) {
                return true;
              }
            }//foreach
          }//foreach
          return false;
    }


    //tries to find a default controller group for this method
    //@return array(instance, method) or array() if nothind found
    private function czr_fn_get_default_controller( $controller_ids ) {
          $controller_cb = false;
          foreach ( $this -> controllers as $group => $views_id ) {
            foreach( $controller_ids as $id ) {
              if ( in_array($id, $views_id) ) {
                $controller_cb = $id;
                $controller_group = $group;
                break 2;
              }//if
            }//foreach
          }//foreach
          //return here is no match found
          if ( ! $controller_cb ) {
            do_action( 'czr_dev_notice', 'View : '.$id.'. No control method found.');
            return array();
          }


          //Is this group controller instantiated ?
          if ( ! array_key_exists($controller_group, self::$controllers_instances) ) {
            //this will actually load the class file and instantiate it
            $_instance = $this -> czr_fn_instantiate_group_controller($controller_group);
          } else {
            $_instance = $this -> czr_fn_get_controller_instance($controller_group);
          }

          //stop here if still nothing is instantiated
          if ( ! isset( $_instance) || ! is_object( $_instance ) ) {
            do_action( 'czr_dev_notice', 'View : '.$id.'. The control class for : ' . $controller_group . ' has not been instantiated.' );
            return array();
          }

          //build the method name
          $method_name = "czr_fn_display_view_{$controller_cb}";//ex : czr_fn_display_view_{favicon_control}()


          //make sure we have a class instance and that the requested controller method exists in it
          if ( method_exists( $_instance , $method_name ) )
            return array( $_instance, $method_name );

          do_action( 'czr_dev_notice', 'model : '.$id.'. The method : ' . $method_name . ' has not been found in group controller : '.$controller_group );
          return array();
    }


    //load the class file if exists
    //instantiate the class is exists
    //@param is a string : header, content, footer, modules
    //@return the $instance
    private function czr_fn_instantiate_group_controller( $group ) {
          $_path     = "controllers/class-controller-{$group}.php";
          $_class    = "CZR_controller_{$group}";
          $_instance = false;
          $CZR       = CZR();

          $CZR -> czr_fn_require_once( CZR_PHP_FRONT_PATH . $_path );

          if ( class_exists($_class) ) {
            $_instance = new $_class;
            do_action( 'group_controller_instantiated', $group, $_instance );
          }
          return $_instance;
    }


    //listens to group_controller_instantiated
    //stores the groupd controller instance in the property : self::$controllers_instances
    //@return void()
    public function czr_fn_store_controller_instance( $group , $_instance ) {
          $controller_instances = self::$controllers_instances;
          if ( array_key_exists($group, $controller_instances) )
            return;

          $controller_instances[$group] = $_instance;
          self::$controllers_instances = $controller_instances;
    }


    //get an already instantiated controller group instance
    public function czr_fn_get_controller_instance( $group ) {
          $controller_instances = self::$controllers_instances;
          if ( ! array_key_exists($group, $controller_instances) )
            return;
          return $controller_instances[$group];
    }
  }//end of class
endif;

?>