MS_Factory

Factory class for all Models.

Defined (1)

The class is defined in the following location(s).

/app/class-ms-factory.php  
  1. class MS_Factory { 
  2.  
  3. /** 
  4. * Holds a list of all singleton objects 
  5. * @since 1.0.0 
  6. * @var array 
  7. */ 
  8. static private $Singleton = array(); 
  9.  
  10. /** 
  11. * Used to cache the original blog-ID when using network-wide protection 
  12. * @since 1.0.0 
  13. * @var array 
  14. */ 
  15. static private $Prev_Blog_Id = array(); 
  16.  
  17. /** 
  18. * This is only used for Unit-Testing to reset all cached singleton 
  19. * instances before running a new test. 
  20. * @since 1.0.0 
  21. */ 
  22. static public function _reset() { 
  23. self::$Singleton = array(); 
  24. wp_cache_flush(); 
  25.  
  26. /** 
  27. * Create an MS Object. 
  28. * @since 1.0.0 
  29. * @param string $class The class to create object from. 
  30. * @return object The created object. 
  31. */ 
  32. public static function create( $class, $init_arg = null ) { 
  33. $singletons = array( 
  34. 'MS_Model_Pages',  
  35. 'MS_Model_Settings',  
  36. 'MS_Model_Addon',  
  37. 'MS_Model_Rule',  
  38. 'MS_Model_Simulate',  
  39. ); 
  40.  
  41. $class = trim( $class ); 
  42.  
  43. if ( in_array( $class, $singletons ) ) { 
  44. _doing_it_wrong( 
  45. 'MS_Factory::create()',  
  46. 'This class is a singleton and should be fetched via MS_Factory::load() -> ' . $class,  
  47. '1.0.4.5' 
  48. ); 
  49.  
  50. if ( class_exists( $class ) ) { 
  51. if ( null === $init_arg ) { 
  52. $obj = new $class(); 
  53. } else { 
  54. $obj = new $class( $init_arg ); 
  55. } else { 
  56. throw new Exception( 'Class ' . $class . ' does not exist.' ); 
  57.  
  58. /** 
  59. * Assign a new unique-ID to the object right after loading it. 
  60. * Purpose: 
  61. * This helps us to spot duplicates of the same object. 
  62. * We can also identify objects that were not created by MS_Factory. 
  63. */ 
  64. $obj->_factory_id = uniqid( 'object-' ); 
  65.  
  66. self::prepare_obj( $obj ); 
  67.  
  68. return apply_filters( 
  69. 'ms_factory_create_'. $class,  
  70. $obj 
  71. ); 
  72.  
  73. /** 
  74. * Load a MS Object. 
  75. * @since 1.0.0 
  76. * @param string $class The class to load object from. 
  77. * @param int $model_id Retrieve model object using ID. 
  78. * @return object The retrieved model. 
  79. */ 
  80. public static function load( $class, $model_id = 0, $context = null ) { 
  81. $model = null; 
  82. $class = trim( $class ); 
  83. $model_id = intval( $model_id ); 
  84.  
  85. $key = strtolower( $class . '-' . $model_id ); 
  86. if ( null !== $context ) { 
  87. $key .= '-' . $context; 
  88.  
  89. if ( class_exists( $class ) && ! isset( self::$Singleton[$key] ) ) { 
  90. /** 
  91. * We create a new object here so we can test via instanceof if 
  92. * the object has a certain parent class. 
  93. * The created object might be replaced by the load_from_... 
  94. * function. 
  95. * Tip: The __constructor() functions of these objects should not 
  96. * exist or contain very lightweight code, never attach any 
  97. * filters/hooks, etc. as the object can be dumped a few lines later. 
  98. */ 
  99. $model = new $class( $model_id ); 
  100. $model->before_load(); 
  101.  
  102. if ( $model instanceof MS_Model_Option ) { 
  103. $model = self::load_from_wp_option( $model ); 
  104. } elseif ( $model instanceof MS_Model_CustomPostType ) { 
  105. $model = self::load_from_wp_custom_post_type( $model, $model_id ); 
  106. } elseif ( $model instanceof MS_Model_Member ) { 
  107. $model = self::load_from_wp_user( $model, $model_id ); 
  108. } elseif ( $model instanceof MS_Model_Transient ) { 
  109. $model = self::load_from_wp_transient( $model, $model_id ); 
  110.  
  111. /** 
  112. * Assign a new unique-ID to the object right after loading it. 
  113. * Purpose: 
  114. * This helps us to spot duplicates of the same object. 
  115. * We can also identify objects that were not created by MS_Factory. 
  116. */ 
  117. $model->_factory_id = uniqid( $key . '-' ); 
  118.  
  119. $model->after_load(); 
  120.  
  121. // Store the new object in our singleton collection. 
  122. self::set_singleton( $model, $key, $model_id ); 
  123.  
  124. self::prepare_obj( self::$Singleton[$key] ); 
  125.  
  126. if ( ! isset( self::$Singleton[$key] ) ) { 
  127. self::$Singleton[$key] = null; 
  128.  
  129. return self::$Singleton[$key]; 
  130.  
  131. /** 
  132. * Allows us to manually set/replace a cached singleton object. 
  133. * This function was introduced to store the simulation subscription as 
  134. * a singleton with subscription ID -1 
  135. * @since 1.0.0 
  136. * @param string $key 
  137. * @param any $obj 
  138. */ 
  139. static public function set_singleton( $obj, $key = null, $model_id = null ) { 
  140. $class = get_class( $obj ); 
  141.  
  142. if ( null === $model_id ) { 
  143. $model_id = intval( $obj->id ); 
  144.  
  145. if ( null === $key ) { 
  146. $key = strtolower( $class . '-' . $model_id ); 
  147.  
  148. // This flag is used by MS_Model::store_singleton() 
  149. if ( property_exists( $obj, '_in_cache' ) ) { 
  150. $obj->_in_cache = true; 
  151.  
  152. $obj = apply_filters( 
  153. 'ms_factory_set-' . strtolower( $class ),  
  154. $obj,  
  155. $model_id 
  156. ); 
  157.  
  158. $obj = apply_filters( 
  159. 'ms_factory_set',  
  160. $obj,  
  161. $class,  
  162. $model_id 
  163. ); 
  164.  
  165. self::$Singleton[ $key ] = $obj; 
  166.  
  167. /** 
  168. * Clears the factory cache. 
  169. * @since 1.0.0 
  170. */ 
  171. static public function clear() { 
  172. wp_cache_flush(); 
  173.  
  174. /** 
  175. * Initialize the object after it was created or loaded. 
  176. * @since 1.0.0 
  177. * @param MS_Hook &$obj Any Membership2 object to initialize. 
  178. */ 
  179. static private function prepare_obj( &$obj ) { 
  180. static $Init_Obj = array(); 
  181. static $Init_Class = array(); 
  182.  
  183. // This case only happens during plugin-updates but needs to be handled. 
  184. if ( is_a( $obj, '__PHP_Incomplete_Class' ) ) { 
  185. return false; 
  186.  
  187. // Prepare each single object that was created. 
  188. if ( method_exists( $obj, 'prepare_obj' ) ) { 
  189. if ( ! isset( $Init_Obj[$obj->_factory_id] ) ) { 
  190. $Init_Obj[$obj->_factory_id] = true; 
  191. $obj->prepare_obj(); 
  192.  
  193. // Prepare the first object of each class-type (i.e. "prepare-once"). 
  194. if ( method_exists( $obj, 'prepare_class' ) ) { 
  195. $class = get_class( $obj ); 
  196.  
  197. if ( ! isset( $Init_Class[$class] ) ) { 
  198. $Init_Class[$class] = true; 
  199. $obj->prepare_class(); 
  200.  
  201. /** 
  202. * Load MS_Model_Option object. 
  203. * MS_Model_Option objects are singletons. 
  204. * To support network-wide protection we use our convenience function 
  205. * self::get_option(). 
  206. * @since 1.0.0 
  207. * @param MS_Model_option $model The empty model instance. 
  208. * @return MS_Model_Option The retrieved object. 
  209. */ 
  210. protected static function load_from_wp_option( $model ) { 
  211. $class = get_class( $model ); 
  212.  
  213. $option_key = $model->option_key(); 
  214. $cache = wp_cache_get( $option_key, 'MS_Model_Option' ); 
  215.  
  216. if ( $cache ) { 
  217. $model = $cache; 
  218. } else { 
  219. $settings = self::get_option( $option_key ); 
  220. self::populate_model( $model, $settings ); 
  221.  
  222. return apply_filters( 
  223. 'ms_factory_load_from_wp_option',  
  224. $model,  
  225. $class 
  226. ); 
  227.  
  228. /** 
  229. * Load MS_Model_Transient object. 
  230. * MS_Transient objects are singletons. 
  231. * To support network-wide protection we use our convenience function 
  232. * self::get_transient(). 
  233. * @since 1.0.0 
  234. * @param MS_Model_Transient $model The empty model instance. 
  235. * @return MS_Model_Transient The retrieved object. 
  236. */ 
  237. public static function load_from_wp_transient( $model ) { 
  238. $option_key = $model->option_key(); 
  239. $cache = wp_cache_get( $option_key, 'MS_Model_Transient' ); 
  240.  
  241. if ( $cache ) { 
  242. $model = $cache; 
  243. } else { 
  244. $settings = self::get_transient( $option_key ); 
  245. self::populate_model( $model, $settings ); 
  246.  
  247. return apply_filters( 
  248. 'ms_factory_load_from_wp_transient',  
  249. $model,  
  250. $option_key 
  251. ); 
  252.  
  253. /** 
  254. * Load MS_Model_CustomPostType Objects. 
  255. * Load from post and postmeta. 
  256. * For network-wide protection we get the data from first blog 
  257. * @since 1.0.0 
  258. * @param MS_Model_CustomPostType $model The empty model instance. 
  259. * @param int $model_id The model id to retrieve. 
  260. * @return MS_Model_CustomPostType The retrieved object. 
  261. */ 
  262. protected static function load_from_wp_custom_post_type( $model, $model_id = 0 ) { 
  263. $class = get_class( $model ); 
  264.  
  265. if ( ! empty( $model_id ) ) { 
  266. $cache = wp_cache_get( $model_id, $class ); 
  267.  
  268. if ( $cache ) { 
  269. $model = $cache; 
  270. } else { 
  271. self::select_blog(); 
  272. $post = get_post( $model_id ); 
  273.  
  274. if ( ! empty( $post ) && $model->get_post_type() === $post->post_type ) { 
  275. $post_meta = get_post_meta( $model_id ); 
  276. $post_meta['id'] = array( $post->ID ); 
  277. $post_meta['description'] = array( $post->post_content ); 
  278. $post_meta['user_id'] = array( $post->post_author ); 
  279. self::populate_model( $model, $post_meta, true ); 
  280.  
  281. /** 
  282. * Allow child classes of the CustomPostType model to load 
  283. * custom values from the posts/postmeta table 
  284. * @since 1.0.1.0 
  285. */ 
  286. $model->load_meta_data( $post_meta ); 
  287. $model->load_post_data( $post ); 
  288. } else { 
  289. $model->id = 0; 
  290. self::revert_blog(); 
  291.  
  292. return apply_filters( 
  293. 'ms_factory_load_from_custom_post_type',  
  294. $model,  
  295. $class,  
  296. $model_id 
  297. ); 
  298.  
  299. /** 
  300. * Load MS_Model_Member Object. 
  301. * Load from user and user meta. 
  302. * This data is always network-wide. 
  303. * @since 1.0.0 
  304. * @param MS_Model_Member $model The empty member instance. 
  305. * @param int $user_id The user/member ID. 
  306. * @return MS_Model_Member The retrieved object. 
  307. */ 
  308. protected static function load_from_wp_user( $model, $user_id, $name = null ) { 
  309. $class = get_class( $model ); 
  310. $cache = wp_cache_get( $user_id, $class ); 
  311.  
  312. if ( $cache ) { 
  313. $model = $cache; 
  314. } else { 
  315. $wp_user = new WP_User( $user_id, $name ); 
  316.  
  317. if ( ! empty( $wp_user->ID ) ) { 
  318. $member_details = get_user_meta( $user_id ); 
  319.  
  320. $model->id = $wp_user->ID; 
  321. $model->username = $wp_user->user_login; 
  322. $model->email = $wp_user->user_email; 
  323. $model->name = $wp_user->display_name; 
  324. $model->first_name = $wp_user->first_name; 
  325. $model->last_name = $wp_user->last_name; 
  326. $model->wp_user = $wp_user; 
  327.  
  328. if ( ! $model->name ) { 
  329. if ( $model->first_name ) { 
  330. $model->name = $model->first_name . ' ' . $model->last_name; 
  331. } else { 
  332. $model->name = $wp_user->user_login; 
  333. $model->name = ucwords( strtolower( $model->name ) ); 
  334. $model->name = trim( $model->name ); 
  335.  
  336. /** 
  337. * Manually customize the display name of the user via a filter. 
  338. * @since 1.0.1.2 
  339. * @param string $name The default display name used by M2. 
  340. * @param WP_User $wp_user The user object used to populate the name. 
  341. */ 
  342. $model->name = apply_filters( 
  343. 'ms_model_user_set_name',  
  344. $model->name,  
  345. $wp_user 
  346. ); 
  347.  
  348. // Remove automatic populated values from metadata, if present. 
  349. unset( $member_details['ms_username'] ); 
  350. unset( $member_details['ms_email'] ); 
  351. unset( $member_details['ms_name'] ); 
  352. unset( $member_details['ms_first_name'] ); 
  353. unset( $member_details['ms_last_name'] ); 
  354.  
  355. self::populate_model( $model, $member_details, 'ms_' ); 
  356.  
  357. // Load membership_relationships 
  358. $model->subscriptions = MS_Model_Relationship::get_subscriptions( 
  359. array( 'user_id' => $model->id ) 
  360. ); 
  361.  
  362. return apply_filters( 
  363. 'ms_factory_load_from_wp_user',  
  364. $model,  
  365. $class,  
  366. $user_id 
  367. ); 
  368.  
  369. // 
  370. // ========================================================================= 
  371. // Public helper functions 
  372. // ========================================================================= 
  373. // 
  374.   
  375. /** 
  376. * Populate fields of the model 
  377. * @since 1.0.0 
  378. * @param MS_Model $model 
  379. * @param array $settings 
  380. * @param bool $postmeta 
  381. */ 
  382. static public function populate_model( &$model, $settings, $postmeta = false ) { 
  383. $fields = $model->get_object_vars(); 
  384. $class = get_class( $model ); 
  385. $vars = get_class_vars( $class ); 
  386. $saved_data = array(); 
  387.  
  388. $ignore = isset( $vars['ignore_fields'] ) ? $vars['ignore_fields'] : array(); 
  389. $ignore[] = 'instance'; // Don't deserialize the double-serialized model! 
  390. $ignore[] = 'actions'; 
  391. $ignore[] = 'filters'; 
  392. $ignore[] = 'ignore_fields'; 
  393.  
  394. foreach ( $fields as $field => $val ) { 
  395. if ( '_' === $field[0] || in_array( $field, $ignore ) ) { 
  396. continue; 
  397.  
  398. $value = null; 
  399.  
  400. if ( false === $postmeta ) { 
  401. if ( isset( $settings[ $field ] ) ) { 
  402. $value = $settings[ $field ]; 
  403. } elseif ( isset( $settings[ '_' . $field ] ) ) { 
  404. $value = $settings[ '_' . $field ]; 
  405. } elseif ( true === $postmeta ) { 
  406. if ( isset( $settings[ $field ][0] ) ) { 
  407. $value = $settings[ $field ][ 0 ]; 
  408. } elseif ( isset( $settings[ '_' . $field ][0] ) ) { 
  409. $value = $settings[ '_' . $field ][ 0 ]; 
  410. } elseif ( is_string( $postmeta ) ) { 
  411. if ( isset( $settings[ $postmeta . $field ][0] ) ) { 
  412. $value = $settings[ $postmeta . $field ][ 0 ]; 
  413. } elseif ( isset( $settings[ '_' . $postmeta . $field ][0] ) ) { 
  414. $value = $settings[ '_' . $postmeta . $field ][ 0 ]; 
  415.  
  416. if ( $value ) { 
  417. $value = maybe_unserialize( $value ); 
  418.  
  419. $saved_data[ $field ] = $value; 
  420. if ( null !== $value ) { 
  421. $model->set_field( $field, $value ); 
  422.  
  423. $model->_saved_data = $saved_data; 
  424.  
  425. /** 
  426. * Filter the serialized data collection before it is returned. 
  427. * Typically it is written to database right after this function call,  
  428. * so this hook allows us to modify data before it's written to the DB. 
  429. * @var object $model The completely populated object. 
  430. * @var string $class Class name of the object. 
  431. * @var array $settings The source data (serialized array). 
  432. * @var bool|string $postmeta The post-meta flag defines how the 
  433. * $settings array is formatted. 
  434. */ 
  435. $model = apply_filters( 
  436. 'ms_factory_populate',  
  437. $model,  
  438. $class,  
  439. $settings,  
  440. $postmeta 
  441. ); 
  442.  
  443. $model = apply_filters( 
  444. 'ms_factory_populate-' . strtolower( $class ),  
  445. $model,  
  446. $settings,  
  447. $postmeta 
  448. ); 
  449.  
  450. /** 
  451. * Converts an MS_Model into an array 
  452. * @since 1.0.0 
  453. * @param MS_Model $model 
  454. * @return array 
  455. */ 
  456. static public function serialize_model( &$model ) { 
  457. $data = array(); 
  458. $ignore = array(); 
  459. $class = get_class( $model ); 
  460.  
  461. if ( is_object( $model ) ) { 
  462. if ( method_exists( $model, '__sleep' ) ) { 
  463. $fields = array_flip( $model->__sleep() ); 
  464. } else { 
  465. $fields = $model->get_object_vars(); 
  466.  
  467. $vars = get_class_vars( get_class( $model ) ); 
  468.  
  469. $ignore = isset( $vars['ignore_fields'] ) ? $vars['ignore_fields'] : array(); 
  470. $ignore[] = 'instance'; // Don't double-serialize the model! 
  471. $ignore[] = 'actions'; 
  472. $ignore[] = 'filters'; 
  473. $ignore[] = 'ignore_fields'; 
  474. } else { 
  475. // Value does not need to be serialized. 
  476. return $model; 
  477.  
  478. foreach ( $fields as $field => $dummy ) { 
  479. if ( '_' === $field[0] || in_array( $field, $ignore ) ) { 
  480. continue; 
  481.  
  482. $data[ $field ] = $model->$field; 
  483.  
  484. /** 
  485. * Filter the serialized data collection before it is returned. 
  486. * Typically it is written to database right after this function call,  
  487. * so this hook allows us to modify data before it's written to the DB. 
  488. * @var array $data Serialized data array. 
  489. * @var string $class Class name of the source object. 
  490. * @var object $model The source object (unserialized) 
  491. */ 
  492. $data = apply_filters( 
  493. 'ms_factory_serialize',  
  494. $data,  
  495. $class,  
  496. $model 
  497. ); 
  498.  
  499. $data = apply_filters( 
  500. 'ms_factory_serialize-' . strtolower( $class ),  
  501. $data,  
  502. $model 
  503. ); 
  504.  
  505. ksort( $data ); 
  506. return $data; 
  507.  
  508. // 
  509. // ========================================================================= 
  510. // Wrappers and convenience functions 
  511. // ========================================================================= 
  512. // 
  513.   
  514. /** 
  515. * Wrapper to get an option value (regards network-wide protection mode) 
  516. * @since 1.0.0 
  517. * @param string $key Option Key 
  518. * @return mixed Option value 
  519. */ 
  520. static public function get_option( $key ) { 
  521. if ( MS_Plugin::is_network_wide() ) { 
  522. $settings = get_site_option( $key ); 
  523. } else { 
  524. $settings = get_option( $key ); 
  525.  
  526. return $settings; 
  527.  
  528. /** 
  529. * Wrapper to delete an option value (regards network-wide protection mode) 
  530. * @since 1.0.0 
  531. * @param string $key Option Key 
  532. */ 
  533. static public function delete_option( $key ) { 
  534. if ( MS_Plugin::is_network_wide() ) { 
  535. delete_site_option( $key ); 
  536. } else { 
  537. delete_option( $key ); 
  538.  
  539. /** 
  540. * Wrapper to update an option value (regards network-wide protection mode) 
  541. * @since 1.0.0 
  542. * @param string $key Option Key 
  543. * @param mixed $value New option value 
  544. */ 
  545. static public function update_option( $key, $value ) { 
  546. if ( MS_Plugin::is_network_wide() ) { 
  547. update_site_option( $key, $value ); 
  548. } else { 
  549. update_option( $key, $value ); 
  550.  
  551. /** 
  552. * Wrapper to get an transient value (regards network-wide protection mode) 
  553. * @since 1.0.0 
  554. * @param string $key Transient Key 
  555. * @return mixed Transient value 
  556. */ 
  557. static public function get_transient( $key ) { 
  558. if ( MS_Plugin::is_network_wide() ) { 
  559. $transient = get_site_transient( $key ); 
  560. } else { 
  561. $transient = get_transient( $key ); 
  562.  
  563. return $transient; 
  564.  
  565. /** 
  566. * Wrapper to delete an transient value (regards network-wide protection mode) 
  567. * @since 1.0.0 
  568. * @param string $key Transient Key 
  569. */ 
  570. static public function delete_transient( $key ) { 
  571. if ( MS_Plugin::is_network_wide() ) { 
  572. delete_site_transient( $key ); 
  573. } else { 
  574. delete_transient( $key ); 
  575.  
  576. /** 
  577. * Wrapper to update an transient value (regards network-wide protection mode) 
  578. * @since 1.0.0 
  579. * @param string $key Transient Key 
  580. * @param mixed $value New transient value 
  581. */ 
  582. static public function set_transient( $key, $value, $expiration ) { 
  583. if ( MS_Plugin::is_network_wide() ) { 
  584. set_site_transient( $key, $value, $expiration ); 
  585. } else { 
  586. set_transient( $key, $value, $expiration ); 
  587.  
  588. /** 
  589. * When network wide protection is enabled this will temporarily switch 
  590. * to the main blog to access or change data. 
  591. * Use revert_blog() when done!! 
  592. * This function is a performance-wise much better alternative to the 
  593. * built-in function switch_to_blog() because it does not run all the 
  594. * initialization logic (update user-roles, etc) when switching a blog. 
  595. * @since 1.0.0 
  596. */ 
  597. static public function select_blog( $site_id = null ) { 
  598. global $wpdb; 
  599.  
  600. if ( MS_Plugin::is_network_wide() ) { 
  601. if ( null === $site_id ) { 
  602. if ( defined( 'BLOG_ID_CURRENT_SITE' ) ) { 
  603. $site_id = BLOG_ID_CURRENT_SITE; 
  604. } else { 
  605. $site_id = 1; 
  606. self::$Prev_Blog_Id[] = $GLOBALS['blog_id']; 
  607.  
  608. if ( $GLOBALS['blog_id'] != $site_id ) { 
  609. $GLOBALS['blog_id'] = $site_id; 
  610. $wpdb->set_blog_id( $GLOBALS['blog_id'] ); 
  611. $GLOBALS['table_prefix'] = $wpdb->get_blog_prefix(); 
  612.  
  613. /** 
  614. * Reverts back to the original blog during network wide protection. 
  615. * @since 1.0.0 
  616. */ 
  617. static public function revert_blog() { 
  618. global $wpdb; 
  619.  
  620. if ( MS_Plugin::is_network_wide() ) { 
  621. $site_id = array_pop( self::$Prev_Blog_Id ); 
  622.  
  623. if ( $site_id != $GLOBALS['blog_id'] ) { 
  624. $GLOBALS['blog_id'] = $site_id; 
  625. $wpdb->set_blog_id( $GLOBALS['blog_id'] ); 
  626. $GLOBALS['table_prefix'] = $wpdb->get_blog_prefix(); 
  627.  
  628. /** 
  629. * Returns the blog-id that was loaded by the user. This will return the 
  630. * original blog-id, even when switched to a different blog by calling 
  631. * self::select_blog() 
  632. * @since 1.0.0 
  633. * @return int The requested blog-ID. 
  634. */ 
  635. static public function current_blog_id() { 
  636. $blog_id = get_current_blog_id(); 
  637.  
  638. if ( count( self::$Prev_Blog_Id ) > 0 ) { 
  639. $blog_id = self::$Prev_Blog_Id[0]; 
  640.  
  641. return $blog_id; 
  642.