/lib/util.php

  1. <?php 
  2. /** 
  3. * License: GPLv3 
  4. * License URI: https://www.gnu.org/licenses/gpl.txt 
  5. * Copyright 2012-2017 Jean-Sebastien Morisset (https://surniaulula.com/) 
  6. */ 
  7.  
  8. if ( ! defined( 'ABSPATH' ) ) { 
  9. die( 'These aren\'t the droids you\'re looking for...' ); 
  10.  
  11. if ( ! class_exists( 'NgfbUtil' ) && class_exists( 'SucomUtil' ) ) { 
  12.  
  13. class NgfbUtil extends SucomUtil { 
  14.  
  15. protected $uniq_urls = array(); // array to detect duplicate images, etc. 
  16. protected $size_labels = array(); // reference array for image size labels 
  17. protected $force_regen = array( 
  18. 'cache' => null, // cache for returned values 
  19. 'transient' => null, // transient array from/to database 
  20. ); 
  21. protected $sanitize_error_msgs = null; // translated error messages for sanitize_option_value() 
  22. protected $cleared_all_cache = false; 
  23.  
  24. public function __construct( &$plugin ) { 
  25. $this->p =& $plugin; 
  26. if ( $this->p->debug->enabled ) 
  27. $this->p->debug->mark(); 
  28.  
  29. add_action( 'wp', array( &$this, 'add_plugin_image_sizes' ), -100 ); // runs everytime a posts query is triggered from a url 
  30. add_action( 'current_screen', array( &$this, 'add_plugin_image_sizes' ), -100 ); 
  31. add_action( 'wp_scheduled_delete', array( &$this, 'delete_expired_db_transients' ) ); 
  32.  
  33. // the "current_screen" action hook is not called when editing / saving an image 
  34. // hook the "image_editor_save_pre" filter as to add image sizes for that attachment / post 
  35. add_filter( 'image_save_pre', array( &$this, 'image_editor_save_pre_image_sizes' ), -100, 2 ); // filter deprecated in wp 3.5 
  36. add_filter( 'image_editor_save_pre', array( &$this, 'image_editor_save_pre_image_sizes' ), -100, 2 ); 
  37.  
  38. // called from several class __construct() methods to hook their filters 
  39. public function add_plugin_filters( $class, $filters, $prio = 10, $lca = '' ) { 
  40. $this->add_plugin_hooks( 'filter', $class, $filters, $prio, $lca ); 
  41.  
  42. public function add_plugin_actions( $class, $actions, $prio = 10, $lca = '' ) { 
  43. $this->add_plugin_hooks( 'action', $class, $actions, $prio, $lca ); 
  44.  
  45. protected function add_plugin_hooks( $type, $class, $hook_list, $prio, $lca ) { 
  46. $lca = $lca === '' ? $this->p->cf['lca'] : $lca; // default lca is '' 
  47.  
  48. foreach ( $hook_list as $name => $val ) { 
  49. if ( ! is_string( $name ) ) { 
  50. if ( $this->p->debug->enabled ) 
  51. $this->p->debug->log( $name.' => '.$val.' '.$type.' skipped: filter name must be a string' ); 
  52. continue; 
  53.  
  54. /** 
  55. * example: 
  56. * 'json_data_https_schema_org_website' => 5 
  57. */ 
  58. if ( is_int( $val ) ) { 
  59. $arg_nums = $val; 
  60. $hook_name = SucomUtil::sanitize_hookname( $lca.'_'.$name ); 
  61. $method_name = SucomUtil::sanitize_hookname( $type.'_'.$name ); 
  62.  
  63. call_user_func( 'add_'.$type, $hook_name, array( &$class, $method_name ), $prio, $arg_nums ); 
  64.  
  65. if ( $this->p->debug->enabled ) 
  66. $this->p->debug->log( 'added '.$method_name.' (method) '.$type, 3 ); 
  67. /** 
  68. * example: 
  69. * 'add_schema_meta_array' => '__return_false' 
  70. */ 
  71. } elseif ( is_string( $val ) ) { 
  72. $arg_nums = 1; 
  73. $hook_name = SucomUtil::sanitize_hookname( $lca.'_'.$name ); 
  74. $function_name = SucomUtil::sanitize_hookname( $val ); 
  75.  
  76. call_user_func( 'add_'.$type, $hook_name, $function_name, $prio, $arg_nums ); 
  77.  
  78. if ( $this->p->debug->enabled ) 
  79. $this->p->debug->log( 'added '.$function_name.' (function) '.$type, 3 ); 
  80. /** 
  81. * example: 
  82. * 'json_data_https_schema_org_article' => array( 
  83. * 'json_data_https_schema_org_article' => 5,  
  84. * 'json_data_https_schema_org_newsarticle' => 5,  
  85. * 'json_data_https_schema_org_techarticle' => 5,  
  86. * ) 
  87. */ 
  88. } elseif ( is_array( $val ) ) { 
  89. $method_name = SucomUtil::sanitize_hookname( $type.'_'.$name ); 
  90. foreach ( $val as $hook_name => $arg_nums ) { 
  91. $hook_name = SucomUtil::sanitize_hookname( $lca.'_'.$hook_name ); 
  92.  
  93. call_user_func( 'add_'.$type, $hook_name, array( &$class, $method_name ), $prio, $arg_nums ); 
  94.  
  95. if ( $this->p->debug->enabled ) 
  96. $this->p->debug->log( 'added '.$method_name.' (method) '.$type.' to '.$hook_name, 3 ); 
  97.  
  98. public function get_image_size_label( $size_name ) { // ngfb-opengraph 
  99. if ( ! empty( $this->size_labels[$size_name] ) ) 
  100. return $this->size_labels[$size_name]; 
  101. else return $size_name; 
  102.  
  103. public function image_editor_save_pre_image_sizes( $image, $post_id = false ) { 
  104. if ( empty( $post_id ) ) 
  105. return $image; 
  106.  
  107. $mod = $this->p->m['util']['post']->get_mod( $post_id ); 
  108. $this->add_plugin_image_sizes( false, array(), $mod, true ); 
  109.  
  110. return $image; 
  111.  
  112. // can be called directly and from the "wp" and "current_screen" actions 
  113. // this method does not return a value, so do not use as a filter 
  114. public function add_plugin_image_sizes( $wp_obj = false, $sizes = array(), &$mod = false, $filter = true ) { 
  115. /** 
  116. * Allow various plugin extensions to provide their image names, labels, etc. 
  117. * The first dimension array key is the option name prefix by default. 
  118. * You can also include the width, height, crop, crop_x, and crop_y values. 
  119. * 
  120. * Array ( 
  121. * [og_img] => Array ( 
  122. * [name] => opengraph 
  123. * [label] => Open Graph Image Dimensions 
  124. * ) 
  125. * [p_img] => Array ( 
  126. * [name] => richpin 
  127. * [label] => Rich Pin Image Dimensions 
  128. * )  
  129. * ) 
  130. */ 
  131. if ( $this->p->debug->enabled ) 
  132. $this->p->debug->mark( 'define image sizes' ); // begin timer 
  133.  
  134. $use_post = false; 
  135. $lca = $this->p->cf['lca']; 
  136. $aop = $this->p->check->aop( $lca, true, $this->p->avail['*']['p_dir'] ); 
  137.  
  138. // $mod is preferred but not required 
  139. // $mod = true | false | post_id | $mod array 
  140. if ( ! is_array( $mod ) ) { 
  141. if ( $this->p->debug->enabled ) { 
  142. $this->p->debug->log( 'optional call to get_page_mod()' ); 
  143. $mod = $this->get_page_mod( $use_post, $mod, $wp_obj ); 
  144.  
  145. $md_opts = array(); 
  146.  
  147. if ( $filter === true ) { 
  148. $sizes = apply_filters( $this->p->cf['lca'].'_plugin_image_sizes',  
  149. $sizes, $mod, SucomUtil::get_crawler_name() ); 
  150.  
  151. if ( empty( $mod['id'] ) ) { 
  152. if ( $this->p->debug->enabled ) 
  153. $this->p->debug->log( 'module id is unknown' ); 
  154. } elseif ( empty( $mod['name'] ) ) { 
  155. if ( $this->p->debug->enabled ) 
  156. $this->p->debug->log( 'module name is unknown' ); 
  157. // custom filters may use image sizes, so don't filter/cache the meta options 
  158. } elseif ( ! empty( $mod['id'] ) && ! empty( $mod['obj'] ) && $aop ) { 
  159. // returns an empty string if no meta found 
  160. $md_opts = $mod['obj']->get_options( $mod['id'], false, false ); // $filter_opts = false 
  161.  
  162. foreach( $sizes as $opt_prefix => $size_info ) { 
  163.  
  164. if ( ! is_array( $size_info ) ) { 
  165. $save_name = empty( $size_info ) ?  
  166. $opt_prefix : $size_info; 
  167. $size_info = array(  
  168. 'name' => $save_name,  
  169. 'label' => $save_name 
  170. ); 
  171. } elseif ( ! empty( $size_info['prefix'] ) ) // allow for alternate option prefix 
  172. $opt_prefix = $size_info['prefix']; 
  173.  
  174. foreach ( array( 'width', 'height', 'crop', 'crop_x', 'crop_y' ) as $key ) { 
  175. if ( isset( $size_info[$key] ) ) // prefer existing info from filters 
  176. continue; 
  177. elseif ( isset( $md_opts[$opt_prefix.'_'.$key] ) ) // use post meta if available 
  178. $size_info[$key] = $md_opts[$opt_prefix.'_'.$key]; 
  179. elseif ( isset( $this->p->options[$opt_prefix.'_'.$key] ) ) // current plugin settings 
  180. $size_info[$key] = $this->p->options[$opt_prefix.'_'.$key]; 
  181. else { 
  182. if ( ! isset( $def_opts ) ) { // only read once if necessary 
  183. if ( $this->p->debug->enabled ) 
  184. $this->p->debug->log( 'getting default option values' ); 
  185. $def_opts = $this->p->opt->get_defaults(); 
  186. $size_info[$key] = $def_opts[$opt_prefix.'_'.$key]; // fallback to default value 
  187. if ( $key === 'crop' ) // make sure crop is true or false 
  188. $size_info[$key] = empty( $size_info[$key] ) ? 
  189. false : true; 
  190.  
  191. if ( $size_info['width'] > 0 && $size_info['height'] > 0 ) { 
  192.  
  193. // preserve compatibility with older wordpress versions, use true or false when possible 
  194. if ( $size_info['crop'] === true &&  
  195. ( $size_info['crop_x'] !== 'center' || $size_info['crop_y'] !== 'center' ) ) { 
  196.  
  197. global $wp_version; 
  198. if ( version_compare( $wp_version, 3.9, '>=' ) ) { 
  199. $size_info['crop'] = array( $size_info['crop_x'], $size_info['crop_y'] ); 
  200.  
  201. // allow custom function hooks to make changes 
  202. if ( $filter === true ) 
  203. $size_info = apply_filters( $this->p->cf['lca'].'_size_info_'.$size_info['name'],  
  204. $size_info, $mod['id'], $mod['name'] ); 
  205.  
  206. // a lookup array for image size labels, used in image size error messages 
  207. $this->size_labels[$this->p->cf['lca'].'-'.$size_info['name']] = $size_info['label']; 
  208.  
  209. add_image_size( $this->p->cf['lca'].'-'.$size_info['name'],  
  210. $size_info['width'], $size_info['height'], $size_info['crop'] ); 
  211.  
  212. if ( $this->p->debug->enabled ) 
  213. $this->p->debug->log( 'image size '.$this->p->cf['lca'].'-'.$size_info['name'].' '. 
  214. $size_info['width'].'x'.$size_info['height']. 
  215. ( empty( $size_info['crop'] ) ? '' : ' crop '. 
  216. $size_info['crop_x'].'/'.$size_info['crop_y'] ).' added' ); 
  217. if ( $this->p->debug->enabled ) { 
  218. $this->p->debug->mark( 'define image sizes' ); // end timer 
  219. $this->p->debug->log_arr( 'get_all_image_sizes', SucomUtil::get_image_sizes() ); 
  220.  
  221. public function set_force_regen( $mod, $md_pre = 'og', $value = true ) { 
  222. $regen_key = $this->get_force_regen_key( $mod, $md_pre ); 
  223. if ( $regen_key !== false ) { 
  224. $cache_salt = __CLASS__.'::force_regen_transient'; 
  225. $cache_id = $this->p->cf['lca'].'_'.md5( $cache_salt ); 
  226. if ( $this->force_regen['transient'] === null ) { 
  227. $this->force_regen['transient'] = get_transient( $cache_id ); // load transient if required 
  228. if ( $this->force_regen['transient'] === false ) { // no transient in database 
  229. $this->force_regen['transient'] = array(); 
  230. $this->force_regen['transient'][$regen_key] = $value; 
  231. set_transient( $cache_id, $this->force_regen['transient'], 0 ); // never expire 
  232.  
  233. public function is_force_regen( $mod, $md_pre = 'og' ) { 
  234. $regen_key = $this->get_force_regen_key( $mod, $md_pre ); 
  235. if ( $regen_key !== false ) { 
  236. $cache_salt = __CLASS__.'::force_regen_transient'; 
  237. $cache_id = $this->p->cf['lca'].'_'.md5( $cache_salt ); 
  238. if ( $this->force_regen['transient'] === null ) { 
  239. $this->force_regen['transient'] = get_transient( $cache_id ); // load transient if required 
  240. if ( $this->force_regen['transient'] === false ) { // no transient in database 
  241. return false; 
  242. if ( isset( $this->force_regen['cache'][$regen_key] ) ) { // previously returned value 
  243. return $this->force_regen['cache'][$regen_key]; 
  244. if ( isset( $this->force_regen['transient'][$regen_key] ) ) { 
  245. $this->force_regen['cache'][$regen_key] = $this->force_regen['transient'][$regen_key]; // save value 
  246. unset( $this->force_regen['transient'][$regen_key] ); // unset the regen key and save transient 
  247. if ( empty( $this->force_regen['transient'] ) ) { 
  248. delete_transient( $cache_id ); 
  249. } else { 
  250. set_transient( $cache_id, $this->force_regen['transient'], 0 ); // never expire 
  251. return $this->force_regen['cache'][$regen_key]; // return the cached value 
  252. return false; // not in the cache or transient array 
  253. return false; 
  254.  
  255. // get the force regen transient id for set and get methods 
  256. // $mod = true | false | post_id | $mod array 
  257. public function get_force_regen_key( $mod, $md_pre ) { 
  258. $lca = $this->p->cf['lca']; 
  259.  
  260. if ( is_numeric( $mod ) && $mod > 0 ) // optimize by skipping get_page_mod() 
  261. return 'post_'.$mod.'_regen_'.$md_pre; 
  262.  
  263. // $mod is preferred but not required 
  264. // $mod = true | false | post_id | $mod array 
  265. if ( ! is_array( $mod ) ) { 
  266. if ( $this->p->debug->enabled ) { 
  267. $this->p->debug->log( 'optional call to get_page_mod()' ); 
  268. $mod = $this->get_page_mod( $mod ); 
  269.  
  270. if ( ! empty( $mod['name'] ) && ! empty( $mod['id'] ) ) 
  271. return $mod['name'].'_'.$mod['id'].'_regen_'.$md_pre; 
  272. else return false; 
  273.  
  274. public function add_ptns_to_opts( &$opts = array(), $mixed, $default = 1 ) { 
  275. if ( ! is_array( $mixed ) ) { 
  276. $mixed = array( $mixed => $default ); 
  277. foreach ( $mixed as $opt_pre => $def_val ) { 
  278. foreach ( $this->get_post_types() as $post_type ) { 
  279. $key = $opt_pre.'_'.$post_type->name; 
  280. if ( ! isset( $opts[$key] ) ) { 
  281. $opts[$key] = $def_val; 
  282. return $opts; 
  283.  
  284. // $output = objects | names 
  285. public function get_post_types( $output = 'objects' ) { 
  286. if ( $this->p->debug->enabled ) { 
  287. $this->p->debug->mark(); 
  288. return apply_filters( $this->p->cf['lca'].'_post_types',  
  289. get_post_types( array( 'public' => true ), $output ), $output ); 
  290.  
  291. public function clear_all_cache( $clear_ext = true, $dis_key = false, $dis_time = false ) { 
  292.  
  293. if ( $this->cleared_all_cache ) { // already run once 
  294. return 0; 
  295. } else { 
  296. $this->cleared_all_cache = true; 
  297.  
  298. wp_cache_flush(); // clear non-database transients as well 
  299.  
  300. $this->delete_expired_db_transients( true ); 
  301. $this->delete_all_cache_files(); 
  302. $this->delete_all_column_meta(); 
  303.  
  304. $lca = $this->p->cf['lca']; 
  305. $short = $this->p->cf['plugin'][$lca]['short']; 
  306. $clear_all_msg = sprintf( __( '%s cached files, transient cache, sortable column meta, and the WordPress object cache have all been cleared.',  
  307. 'nextgen-facebook' ), $short ); 
  308.  
  309. if ( $clear_ext ) { 
  310. $ext_cache_msg = __( 'The cache for %s has also been cleared.', 'nextgen-facebook' ); 
  311.  
  312. if ( function_exists( 'w3tc_pgcache_flush' ) ) { // w3 total cache 
  313. w3tc_pgcache_flush(); 
  314. w3tc_objectcache_flush(); 
  315. $clear_all_msg .= ' '.sprintf( $ext_cache_msg, 'W3 Total Cache' ); 
  316.  
  317. if ( function_exists( 'wp_cache_clear_cache' ) ) { // wp super cache 
  318. wp_cache_clear_cache(); 
  319. $clear_all_msg .= ' '.sprintf( $ext_cache_msg, 'WP Super Cache' ); 
  320.  
  321. if ( isset( $GLOBALS['comet_cache'] ) ) { // comet cache 
  322. $GLOBALS['comet_cache']->wipe_cache(); 
  323. $clear_all_msg .= ' '.sprintf( $ext_cache_msg, 'Comet Cache' ); 
  324.  
  325. } elseif ( isset( $GLOBALS['zencache'] ) ) { // zencache 
  326. $GLOBALS['zencache']->wipe_cache(); 
  327. $clear_all_msg .= ' '.sprintf( $ext_cache_msg, 'ZenCache' ); 
  328.  
  329. $clear_all_msg .= ' '.__( 'Site performance may be impacted slightly while all cache objects are rebuilt.', 'nextgen-facebook' ); 
  330.  
  331. $this->p->notice->inf( $clear_all_msg, true, $dis_key, $dis_time ); // can be dismissed depending on args 
  332.  
  333. public function clear_cache_objects( array $transients, array $wp_objects ) { 
  334. $deleted = 0; 
  335. $lca = $this->p->cf['lca']; 
  336. foreach ( $transients as $group => $arr ) { 
  337. foreach ( $arr as $val ) { 
  338. if ( ! empty( $val ) ) { 
  339. $cache_salt = $group.'('.$val.')'; 
  340. $cache_id = $lca.'_'.md5( $cache_salt ); 
  341. if ( delete_transient( $cache_id ) ) { 
  342. if ( $this->p->debug->enabled ) 
  343. $this->p->debug->log( 'cleared cache transient '.$cache_salt ); 
  344. $deleted++; 
  345. foreach ( $wp_objects as $group => $arr ) { 
  346. foreach ( $arr as $val ) { 
  347. if ( ! empty( $val ) ) { 
  348. $cache_salt = $group.'('.$val.')'; 
  349. $cache_id = $lca.'_'.md5( $cache_salt ); 
  350. if ( wp_cache_delete( $cache_id, $group ) ) { 
  351. if ( $this->p->debug->enabled ) 
  352. $this->p->debug->log( 'cleared cache object '.$cache_salt ); 
  353. $deleted++; 
  354. return $deleted; 
  355.  
  356. public function delete_expired_db_transients( $all = false ) {  
  357. global $wpdb; 
  358. $lca = $this->p->cf['lca']; 
  359. $current_time = isset ( $_SERVER['REQUEST_TIME'] ) ? 
  360. (int) $_SERVER['REQUEST_TIME'] : time() ;  
  361. if ( $all ) { 
  362. $prefix = '_transient_'; // clear all transients, even if no timeout value 
  363. $dbquery = 'SELECT option_name FROM '.$wpdb->options. 
  364. ' WHERE option_name LIKE \''.$prefix.$lca.'_%\';'; 
  365. } else { 
  366. $prefix = '_transient_timeout_'; 
  367. $dbquery = 'SELECT option_name FROM '.$wpdb->options. 
  368. ' WHERE option_name LIKE \''.$prefix.$lca.'_%\''. 
  369. ' AND option_value < '.$current_time.';'; // expiration time older than current time 
  370. $expired = $wpdb->get_col( $dbquery );  
  371. $deleted = 0; 
  372. foreach( $expired as $option_name ) {  
  373. $transient_name = str_replace( $prefix, '', $option_name ); 
  374. /** 
  375. * If clearing all transients, skip the shortened URL transients  
  376. * unless the "Clear Short URLs on Clear All Cache" option is checked. 
  377. */ 
  378. if ( $all ) { 
  379. if ( empty( $this->p->cf['plugin_clear_short_urls'] ) &&  
  380. strpos( $transient_name, $lca.'_sh' ) === 0 ) 
  381. continue; 
  382. if ( delete_transient( $transient_name ) ) 
  383. $deleted++; 
  384. return $deleted; 
  385.  
  386. public function delete_all_cache_files() { 
  387. $uca = strtoupper( $this->p->cf['lca'] ); 
  388. $cache_dir = constant( $uca.'_CACHEDIR' ); 
  389. $deleted = 0; 
  390. if ( ! $dh = @opendir( $cache_dir ) ) { 
  391. $this->p->notice->err( sprintf( __( 'Failed to open directory %s for reading.',  
  392. 'nextgen-facebook' ), $cache_dir ) ); 
  393. } else { 
  394. while ( $file_name = @readdir( $dh ) ) { 
  395. $cache_file = $cache_dir.$file_name; 
  396. if ( ! preg_match( '/^(\..*|index\.php)$/', $file_name ) && is_file( $cache_file ) ) { 
  397. if ( @unlink( $cache_file ) ) { 
  398. if ( $this->p->debug->enabled ) 
  399. $this->p->debug->log( 'removed cache file '.$cache_file ); 
  400. $deleted++; 
  401. } else {  
  402. if ( $this->p->debug->enabled ) 
  403. $this->p->debug->log( 'error removing cache file '.$cache_file ); 
  404. if ( is_admin() ) 
  405. $this->p->notice->err( sprintf( __( 'Error removing cache file %s.',  
  406. 'nextgen-facebook' ), $cache_file ) ); 
  407. closedir( $dh ); 
  408. return $deleted; 
  409.  
  410. public function delete_all_column_meta() { 
  411. $col_meta_keys = NgfbMeta::get_column_meta_keys(); 
  412.  
  413. foreach ( $col_meta_keys as $col_idx => $meta_key ) { 
  414. delete_post_meta_by_key( $meta_key ); 
  415.  
  416. foreach ( get_users() as $user ) { 
  417. foreach ( $col_meta_keys as $col_idx => $meta_key ) { 
  418. delete_user_meta( $user->ID, $meta_key ); 
  419.  
  420. foreach ( NgfbTerm::get_public_terms() as $term_id ) { 
  421. foreach ( $col_meta_keys as $col_idx => $meta_key ) { 
  422. NgfbTerm::delete_term_meta( $term_id, $meta_key ); 
  423.  
  424. public function get_article_topics() { 
  425. if ( $this->p->debug->enabled ) 
  426. $this->p->debug->mark(); 
  427.  
  428. $lca = $this->p->cf['lca']; 
  429. $cache_salt = __METHOD__.'('.NGFB_TOPICS_LIST.')'; 
  430. $cache_id = $lca.'_'.md5( $cache_salt ); 
  431. $cache_exp = (int) apply_filters( $lca.'_cache_expire_article_topics',  
  432. $this->p->options['plugin_topics_cache_exp'] ); 
  433.  
  434. if ( $this->p->debug->enabled ) { 
  435. $this->p->debug->log( 'transient cache salt '.$cache_salt ); 
  436.  
  437. if ( $cache_exp > 0 ) { 
  438. $topics = get_transient( $cache_id ); 
  439. if ( is_array( $topics ) ) { 
  440. if ( $this->p->debug->enabled ) { 
  441. $this->p->debug->log( 'article topics retrieved from transient '.$cache_id ); 
  442. return $topics; 
  443.  
  444. if ( ( $topics = file( NGFB_TOPICS_LIST, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ) ) === false ) { 
  445. if ( $this->p->debug->enabled ) { 
  446. $this->p->debug->log( 'error reading %s article topic list file' ); 
  447. if ( is_admin() ) { 
  448. $this->p->notice->err( sprintf( __( 'Error reading %s article topic list file.',  
  449. 'nextgen-facebook' ), NGFB_TOPICS_LIST ) ); 
  450. return $topics; 
  451.  
  452. $topics = apply_filters( $lca.'_article_topics', $topics ); 
  453. natsort( $topics ); 
  454. $topics = array_merge( array( 'none' ), $topics ); // after sorting the array, put 'none' first 
  455.  
  456. if ( $cache_exp > 0 ) { 
  457. set_transient( $cache_id, $topics, $cache_exp ); 
  458. if ( $this->p->debug->enabled ) 
  459. $this->p->debug->log( 'article topics saved to transient '. 
  460. $cache_id.' ('.$cache_exp.' seconds)'); 
  461.  
  462. return $topics; 
  463.  
  464. public function sanitize_option_value( $key, $val, $def_val, $network = false, &$mod = false ) { 
  465.  
  466. if ( is_array( $val ) ) { // just in case 
  467. return $val; // stop here 
  468.  
  469. // remove multiples, localization, and status for more generic match 
  470. $option_key = preg_replace( '/(_[0-9]+)?(#.*|:[0-9]+)?$/', '', $key ); 
  471.  
  472. // hooked by several extensions 
  473. $option_type = apply_filters( $this->p->cf['lca'].'_option_type',  
  474. false, $option_key, $network, $mod ); 
  475.  
  476. // translate error messages only once 
  477. if ( $this->sanitize_error_msgs === null ) { 
  478. $this->sanitize_error_msgs = array( 
  479. 'url' => __( 'The value of option \'%s\' must be a URL - resetting option to default value.',  
  480. 'nextgen-facebook' ),  
  481. 'csv_urls' => __( 'The value of option \'%s\' must be a comma-delimited list of URL(s) - resetting option to default value.',  
  482. 'nextgen-facebook' ),  
  483. 'numeric' => __( 'The value of option \'%s\' must be numeric - resetting option to default value.',  
  484. 'nextgen-facebook' ),  
  485. 'pos_num' => __( 'The value of option \'%1$s\' must be equal to or greather than %2$s - resetting option to default value.',  
  486. 'nextgen-facebook' ),  
  487. 'blank_num' => __( 'The value of option \'%s\' must be blank or numeric - resetting option to default value.',  
  488. 'nextgen-facebook' ),  
  489. 'api_key' => __( 'The value of option \'%s\' must be alpha-numeric - resetting option to default value.',  
  490. 'nextgen-facebook' ),  
  491. 'color' => __( 'The value of option \'%s\' must be a CSS color code - resetting option to default value.',  
  492. 'nextgen-facebook' ),  
  493. 'date' => __( 'The value of option \'%s\' must be a yyyy-mm-dd date - resetting option to default value.',  
  494. 'nextgen-facebook' ),  
  495. 'time' => __( 'The value of option \'%s\' must be a hh:mm time - resetting option to default value.',  
  496. 'nextgen-facebook' ),  
  497. 'html' => __( 'The value of option \'%s\' must be HTML code - resetting option to default value.',  
  498. 'nextgen-facebook' ),  
  499. 'not_blank' => __( 'The value of option \'%s\' cannot be an empty string - resetting option to default value.',  
  500. 'nextgen-facebook' ),  
  501. ); 
  502.  
  503. // pre-filter most values to remove html 
  504. switch ( $option_type ) { 
  505. case 'ignore': 
  506. return $val; // stop here 
  507. break; 
  508. case 'html': // leave html, css, and javascript code blocks as-is 
  509. case 'code': 
  510. break; 
  511. default: 
  512. $val = wp_filter_nohtml_kses( $val ); // strips all the HTML in the content 
  513. $val = stripslashes( $val ); // strip slashes added by wp_filter_nohtml_kses() 
  514. break; 
  515.  
  516. // optional cast on return 
  517. $cast_int = false; 
  518.  
  519. switch ( $option_type ) { 
  520. // must be empty or texturized  
  521. case 'textured': 
  522. if ( $val !== '' ) { 
  523. $val = trim( wptexturize( ' '.$val.' ' ) ); 
  524. break; 
  525.  
  526. // must be empty or a url 
  527. case 'url': 
  528. if ( $val !== '' ) { 
  529. if ( filter_var( $val, FILTER_VALIDATE_URL ) === false ) { 
  530. $this->p->notice->err( sprintf( $this->sanitize_error_msgs[$option_type], $key ) ); 
  531. $val = $def_val; 
  532. break; 
  533.  
  534. // strip leading urls off facebook usernames 
  535. case 'url_base': 
  536. if ( $val !== '' ) { 
  537. $val = preg_replace( '/(http|https):\/\/[^\/]*?\//', '', $val ); 
  538. break; 
  539.  
  540. case 'csv_blank': 
  541. if ( $val !== '' ) { 
  542. $val = implode( ', ', SucomUtil::explode_csv( $val ) ); 
  543. break; 
  544.  
  545. case 'csv_urls': 
  546. if ( $val !== '' ) { 
  547. $parts = array(); 
  548. foreach ( SucomUtil::explode_csv( $val ) as $part ) { 
  549. if ( filter_var( $part, FILTER_VALIDATE_URL ) === false ) { 
  550. $this->p->notice->err( sprintf( $this->sanitize_error_msgs[$option_type], $key ) ); 
  551. $val = $def_val; 
  552. break; 
  553. } else $parts[] = $part; 
  554. $val = implode( ', ', $parts ); 
  555. break; 
  556.  
  557. // twitter-style usernames (prepend with an @ character) 
  558. case 'at_name': 
  559. if ( $val !== '' ) { 
  560. $val = SucomUtil::get_at_name( $val ); 
  561. break; 
  562.  
  563. // must be integer / numeric 
  564. case 'integer': 
  565. $cast_int = true; 
  566. // no break 
  567. case 'numeric': 
  568. if ( ! is_numeric( $val ) ) { 
  569. $this->p->notice->err( sprintf( $this->sanitize_error_msgs['numeric'], $key ) ); 
  570. $val = $def_val; 
  571. break; 
  572.  
  573. // integer / numeric options that must be 1 or more (not zero) 
  574. case 'pos_int': 
  575. case 'img_width': // image height, subject to minimum value (typically, at least 200px) 
  576. case 'img_height': // image height, subject to minimum value (typically, at least 200px) 
  577. $cast_int = true; 
  578. // no break 
  579. case 'pos_num': 
  580. if ( $option_type === 'img_width' ) { 
  581. $min_int = $this->p->cf['head']['limit_min']['og_img_width']; 
  582. } elseif ( $option_type === 'img_height' ) { 
  583. $min_int = $this->p->cf['head']['limit_min']['og_img_height']; 
  584. } else { 
  585. $min_int = 1; 
  586. if ( ! empty( $mod['name'] ) && $val === '' ) { // custom meta options can be empty 
  587. $cast_int = false; 
  588. } elseif ( ! is_numeric( $val ) || $val < $min_int ) { 
  589. $this->p->notice->err( sprintf( $this->sanitize_error_msgs['pos_num'], $key, $min_int ) ); 
  590. $val = $def_val; 
  591. break; 
  592.  
  593. // must be blank or integer / numeric 
  594. case 'blank_int': 
  595. $cast_int = true; 
  596. // no break 
  597. case 'blank_num': 
  598. if ( $val === '' ) { 
  599. $cast_int = false; 
  600. } else { 
  601. if ( ! is_numeric( $val ) ) { 
  602. $this->p->notice->err( sprintf( $this->sanitize_error_msgs['blank_num'], $key ) ); 
  603. $val = $def_val; 
  604. break; 
  605.  
  606. // empty or alpha-numeric uppercase (hyphens are allowed as well) 
  607. case 'auth_id': 
  608. // silently convert illegal characters to single hyphens and trim excess 
  609. $val = trim( preg_replace( '/[^A-Z0-9\-]+/', '-', $val ), '-' ); 
  610. break; 
  611.  
  612. // empty or alpha-numeric (upper or lower case), plus underscores 
  613. case 'api_key': 
  614. $val = trim( $val ); 
  615. if ( $val !== '' && preg_match( '/[^a-zA-Z0-9_]/', $val ) ) { 
  616. $this->p->notice->err( sprintf( $this->sanitize_error_msgs[$option_type], $key ) ); 
  617. $val = $def_val; 
  618. break; 
  619.  
  620. case 'color': 
  621. case 'date': 
  622. case 'time': 
  623. $val = trim( $val ); 
  624. if ( $option_type === 'color' ) { 
  625. $fmt = '/^#[a-fA-f0-9]{6, 6}$/'; // color #000000 
  626. } elseif ( $option_type === 'date' ) { 
  627. $fmt = '/^[0-9]{4, 4}-[0-9]{2, 2}-[0-9]{2, 2}$/'; // date yyyy-mm-dd 
  628. } else { 
  629. $fmt = '/^[0-9]{2, 2}:[0-9]{2, 2}$/'; // time hh:mm 
  630. if ( $val !== '' && ! preg_match( $fmt, $val ) ) { 
  631. $this->p->notice->err( sprintf( $this->sanitize_error_msgs[$option_type], $key ) ); 
  632. $val = $def_val; 
  633. break; 
  634.  
  635. // text strings that can be blank 
  636. case 'ok_blank': 
  637. if ( $val !== '' ) { 
  638. $val = trim( $val ); 
  639. break; 
  640.  
  641. // text strings that can be blank (line breaks are removed) 
  642. case 'desc': 
  643. case 'one_line': 
  644. if ( $val !== '' ) { 
  645. $val = trim( preg_replace( '/[\s\n\r]+/s', ' ', $val ) ); 
  646. break; 
  647.  
  648. // empty string or must include at least one HTML tag 
  649. case 'html': 
  650. if ( $val !== '' ) { 
  651. $val = trim( $val ); 
  652. if ( ! preg_match( '/<.*>/', $val ) ) { 
  653. $this->p->notice->err( sprintf( $this->sanitize_error_msgs['html'], $key ) ); 
  654. $val = $def_val; 
  655. break; 
  656.  
  657. // options that cannot be blank (aka empty string) 
  658. case 'code': 
  659. case 'not_blank': 
  660. if ( $val === '' ) { 
  661. $this->p->notice->err( sprintf( $this->sanitize_error_msgs['not_blank'], $key ) ); 
  662. $val = $def_val; 
  663. break; 
  664.  
  665. // everything else is a 1 or 0 checkbox option  
  666. case 'checkbox': 
  667. default: 
  668. if ( $def_val === 0 || $def_val === 1 ) { // make sure the default option is also a 1 or 0, just in case 
  669. $val = empty( $val ) ? 0 : 1; 
  670. break; 
  671.  
  672. if ( $cast_int ) { 
  673. return (int) $val; 
  674. } else { 
  675. return $val; 
  676.  
  677. // query examples: 
  678. // /html/head/link|/html/head/meta 
  679. // /html/head/meta[starts-with(@property, 'og:video:')] 
  680. public function get_head_meta( $request, $query = '/html/head/meta', $remove_self = false ) { 
  681. if ( empty( $query ) ) 
  682. return false; 
  683.  
  684. if ( strpos( $request, '<' ) === 0 ) { // check for HTML content 
  685. if ( $this->p->debug->enabled ) 
  686. $this->p->debug->log( 'using html submitted in the request argument' ); 
  687. $html = $request; 
  688. } elseif ( strpos( $request, '://' ) === false ) { 
  689. if ( $this->p->debug->enabled ) 
  690. $this->p->debug->log( 'exiting early: request argument is not html or valid url' ); 
  691. return false; 
  692. // fetch the webpage content and save it as a transient 
  693. } elseif ( ( $html = $this->p->cache->get( $request, 'raw', 'transient' ) ) === false ) { 
  694. if ( $this->p->debug->enabled ) 
  695. $this->p->debug->log( 'exiting early: error caching '.$request ); 
  696. if ( is_admin() ) 
  697. $this->p->notice->err( sprintf( __( 'Error retrieving webpage from <a href="%1$s">%1$s</a>.',  
  698. 'nextgen-facebook' ), $request ) ); 
  699. return false; 
  700. } elseif ( empty( $html ) ) { 
  701. if ( $this->p->debug->enabled ) 
  702. $this->p->debug->log( 'exiting early: html for '.$request.' is empty' ); 
  703. if ( is_admin() ) 
  704. $this->p->notice->err( sprintf( __( 'Webpage retrieved from <a href="%1$s">%1$s</a> is empty.',  
  705. 'nextgen-facebook' ), $request ) ); 
  706. return false; 
  707.  
  708. $ret = array(); 
  709. $lca = $this->p->cf['lca']; 
  710. $html = mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ); // convert to UTF8 
  711.  
  712. if ( $remove_self && strpos( $html, $lca.' meta tags begin' ) !== false ) { // quick check 
  713.  
  714. if ( $this->p->debug->enabled ) { 
  715. $this->p->debug->log( 'removing self meta tags' ); 
  716.  
  717. $mark_prefix = '<(!--[\s\n\r]+|meta[\s\n\r]+name="'.$lca.':mark:(begin|end)"[\s\n\r]+content=")'; 
  718. $mark_suffix = '([\s\n\r]+--|"[\s\n\r]*\/?)>'; // space and slash are optional for html optimizers 
  719.  
  720. $html = preg_replace( '/'.$mark_prefix.$lca.' meta tags begin'.$mark_suffix.'.*'. 
  721. $mark_prefix.$lca.' meta tags end'.$mark_suffix.'/ums', // enable utf8 functionality 
  722. '<!-- '.$lca.' meta tags removed -->', $html, -1, $count ); 
  723.  
  724. if ( ! $count ) { 
  725. if ( is_admin() ) { 
  726. $short = $this->p->cf['plugin'][$lca]['short']; 
  727. $this->p->notice->err( sprintf( __( 'The PHP preg_replace() function failed to remove the %1$s meta tag block — this could be an indication of a problem with PHP\'s PCRE library or an HTML filter corrupting the %1$s meta tags.', 'nextgen-facebook' ), $short ) ); 
  728. return false; 
  729.  
  730. if ( class_exists( 'DOMDocument' ) ) { 
  731. $doc = new DOMDocument(); // since PHP v4.1 
  732.  
  733. if ( function_exists( 'libxml_use_internal_errors' ) ) { // since PHP v5.1 
  734. $libxml_saved_state = libxml_use_internal_errors( true ); 
  735. $doc->loadHTML( $html ); 
  736. libxml_clear_errors(); // clear any HTML parsing errors 
  737. libxml_use_internal_errors( $libxml_saved_state ); 
  738. } else @$doc->loadHTML( $html ); 
  739.  
  740. $xpath = new DOMXPath( $doc ); 
  741. $metas = $xpath->query( $query ); 
  742.  
  743. foreach ( $metas as $m ) { 
  744. $m_atts = array(); // put all attributes in a single array 
  745. foreach ( $m->attributes as $a ) 
  746. $m_atts[$a->name] = $a->value; 
  747. if ( isset( $m->textContent ) ) 
  748. $m_atts['textContent'] = $m->textContent; 
  749. $ret[$m->tagName][] = $m_atts; 
  750. } else $this->missing_php_class_error( 'DOMDocument' ); 
  751.  
  752. return empty( $ret ) ? false : $ret; 
  753.  
  754. public function missing_php_class_error( $classname ) { 
  755. if ( $this->p->debug->enabled ) { 
  756. $this->p->debug->log( $classname.' PHP class is missing' ); 
  757. if ( is_admin() ) { 
  758. $this->p->notice->err( sprintf( __( 'The %1$s PHP class is missing - please contact your hosting provider to install the missing %1$s PHP class.',  
  759. 'nextgen-facebook' ), $classname ) ); 
  760.  
  761. public function get_body_html( $request, $remove_script = true ) { 
  762. $html = ''; 
  763.  
  764. if ( strpos( $request, '//' ) === 0 ) 
  765. $request = self::get_prot().':'.$request; 
  766.  
  767. if ( strpos( $request, '<' ) === 0 ) { // check for HTML content 
  768. if ( $this->p->debug->enabled ) 
  769. $this->p->debug->log( 'using html submitted in the request argument' ); 
  770. $html = $request; 
  771. } elseif ( empty( $request ) ) { 
  772. if ( $this->p->debug->enabled ) 
  773. $this->p->debug->log( 'exiting early: request argument is empty' ); 
  774. return false; 
  775. } elseif ( strpos( $request, 'data:' ) === 0 ) { 
  776. if ( $this->p->debug->enabled ) 
  777. $this->p->debug->log( 'exiting early: request argument is inline data' ); 
  778. return false; 
  779. } elseif ( strpos( $request, '://' ) === false ) { 
  780. if ( $this->p->debug->enabled ) 
  781. $this->p->debug->log( 'exiting early: request argument is not html or valid url' ); 
  782. return false; 
  783. } else { 
  784. if ( $this->p->debug->enabled ) 
  785. $this->p->debug->log( 'fetching body html for '.$request ); 
  786. if ( ( $html = $this->p->cache->get( $request, 'raw', 'transient' ) ) === false ) { 
  787. if ( $this->p->debug->enabled ) 
  788. $this->p->debug->log( 'exiting early: error caching '.$request ); 
  789. return false; 
  790.  
  791. $html = preg_replace( '/^.*<body[^>]*>(.*)<\/body>.*$/Ums', '$1', $html ); 
  792.  
  793. if ( $remove_script ) 
  794. $html = preg_replace( '/<script[^>]*>.*<\/script>/Ums', '', $html ); 
  795.  
  796. return $html; 
  797.  
  798. public function log_is_functions() { 
  799. $is_functions = array(  
  800. 'is_ajax',  
  801. 'is_archive',  
  802. 'is_attachment',  
  803. 'is_author',  
  804. 'is_category',  
  805. 'is_front_page',  
  806. 'is_home',  
  807. 'is_multisite',  
  808. 'is_page',  
  809. 'is_search',  
  810. 'is_single',  
  811. 'is_singular',  
  812. 'is_ssl',  
  813. 'is_tag',  
  814. 'is_tax',  
  815. /** 
  816. * common e-commerce / woocommerce functions 
  817. */ 
  818. 'is_account_page',  
  819. 'is_cart',  
  820. 'is_checkout',  
  821. 'is_checkout_pay_page',  
  822. 'is_product',  
  823. 'is_product_category',  
  824. 'is_product_tag',  
  825. 'is_shop',  
  826. /** 
  827. * other functions 
  828. */ 
  829. 'is_amp_endpoint',  
  830. ); 
  831. $is_functions = apply_filters( $this->p->cf['lca'].'_is_functions', $is_functions ); 
  832. foreach ( $is_functions as $function )  
  833. $this->p->debug->log( $function.'() = '. 
  834. ( function_exists( $function ) &&  
  835. $function() ? 'true' : 'false' ) ); 
  836.  
  837. // returns true if the default image is forced 
  838. public function force_default_image( array &$mod, $opt_pre = 'og' ) { 
  839. return $this->force_default( 'img', $mod, $opt_pre ); 
  840.  
  841. // returns true if the default video is forced 
  842. public function force_default_video( array &$mod, $opt_pre = 'og' ) { 
  843. return $this->force_default( 'vid', $mod, $opt_pre ); 
  844.  
  845. // $type = author | img | vid 
  846. public function force_default( $type, array &$mod, $opt_pre = 'og') { 
  847. $lca = $this->p->cf['lca']; 
  848. $def = array(); 
  849.  
  850. // setup default true / false values 
  851. foreach ( array( 'id', 'url', 'on_index', 'on_search' ) as $key ) 
  852. $def[$key] = apply_filters( $lca.'_'.$opt_pre.'_default_'.$type.'_'.$key,  
  853. ( isset( $this->p->options[$opt_pre.'_def_'.$type.'_'.$key] ) ?  
  854. $this->p->options[$opt_pre.'_def_'.$type.'_'.$key] : null ) ); 
  855.  
  856. if ( empty( $def['id'] ) && empty( $def['url'] ) ) // save time - if no default media, then return false 
  857. $ret = false; 
  858. elseif ( $mod['is_post'] ) // check for singular pages first 
  859. $ret = false; 
  860. elseif ( $mod['is_user'] ) // check for user pages first 
  861. $ret = false; 
  862. elseif ( ! empty( $def['on_index'] ) && ( $mod['is_home_index'] || $mod['is_term'] || SucomUtil::is_archive_page() ) ) 
  863. $ret = true; 
  864. elseif ( ! empty( $def['on_search'] ) && is_search() ) 
  865. $ret = true; 
  866. else $ret = false; 
  867.  
  868. // 'ngfb_force_default_img' is hooked by the woocommerce module (false for product category and tag pages) 
  869. $ret = apply_filters( $this->p->cf['lca'].'_force_default_'.$type, $ret, $mod, $opt_pre ); 
  870.  
  871. if ( $ret && $this->p->debug->enabled ) 
  872. $this->p->debug->log( 'default '.$type.' is forced' ); 
  873.  
  874. return $ret; 
  875.  
  876. public static function save_all_times( $lca, $version ) { 
  877. self::save_time( $lca, $version, 'update', $version ); // $protect only if same version 
  878. self::save_time( $lca, $version, 'install', true ); // $protect = true 
  879. self::save_time( $lca, $version, 'activate' ); // always update timestamp 
  880.  
  881. // $protect = true/false/version 
  882. public static function save_time( $lca, $version, $type, $protect = false ) { 
  883. if ( ! is_bool( $protect ) ) { 
  884. if ( ! empty( $protect ) ) { 
  885. if ( ( $ts_version = self::get_option_key( NGFB_TS_NAME, $lca.'_'.$type.'_version' ) ) !== null && 
  886. version_compare( $ts_version, $protect, '==' ) ) { 
  887. $protect = true; 
  888. } else { 
  889. $protect = false; 
  890. } else { 
  891. $protect = true; // just in case 
  892. if ( ! empty( $version ) ) { 
  893. self::update_option_key( NGFB_TS_NAME, $lca.'_'.$type.'_version', $version, $protect ); 
  894. self::update_option_key( NGFB_TS_NAME, $lca.'_'.$type.'_time', time(), $protect ); 
  895.  
  896. // get the timestamp array and perform a quick sanity check 
  897. public function get_all_times() { 
  898. $has_changed = false; 
  899. $ts = get_option( NGFB_TS_NAME, array() ); 
  900. foreach ( $this->p->cf['plugin'] as $lca => $info ) { 
  901. if ( empty( $info['version'] ) ) { 
  902. continue; 
  903. foreach ( array( 'update', 'install', 'activate' ) as $type ) { 
  904. if ( empty( $ts[$lca.'_'.$type.'_time'] ) || 
  905. ( $type === 'update' && ( empty( $ts[$lca.'_'.$type.'_version'] ) ||  
  906. version_compare( $ts[$lca.'_'.$type.'_version'], $info['version'], '!=' ) ) ) ) { 
  907. $has_changed = self::save_time( $lca, $info['version'], $type ); 
  908. return $has_changed === false ? 
  909. $ts : get_option( NGFB_TS_NAME, array() ); 
  910.  
  911. public function get_inline_vars() { 
  912. return array( 
  913. '%%request_url%%',  
  914. '%%sharing_url%%',  
  915. '%%short_url%%',  
  916. ); 
  917.  
  918. public function get_inline_vals( $mod = false, &$atts = array() ) { 
  919.  
  920. // $mod is preferred but not required 
  921. // $mod = true | false | post_id | $mod array 
  922. if ( ! is_array( $mod ) ) { 
  923. if ( $this->p->debug->enabled ) { 
  924. $this->p->debug->log( 'optional call to get_page_mod()' ); 
  925. $mod = $this->get_page_mod( $mod ); 
  926.  
  927. if ( isset( $atts['url'] ) ) 
  928. $sharing_url = $atts['url']; 
  929. else $sharing_url = $this->get_sharing_url( $mod,  
  930. ( isset( $atts['add_page'] ) ? $atts['add_page'] : true ),  
  931. ( isset( $atts['src_id'] ) ? $atts['src_id'] : '' ) ); 
  932.  
  933. if ( is_admin() ) 
  934. $request_url = $sharing_url; 
  935. else $request_url = self::get_prot().'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; 
  936.  
  937. $short_url = empty( $atts['short_url'] ) ? 
  938. apply_filters( $this->p->cf['lca'].'_shorten_url',  
  939. $sharing_url, $this->p->options['plugin_shortener'] ) : $atts['short_url']; 
  940.  
  941. return array( 
  942. $request_url, // %%request_url%% 
  943. $sharing_url, // %%sharing_url%% 
  944. $short_url, // %%short_url%% 
  945. ); 
  946.  
  947. // allow the variables and values array to be extended 
  948. // $ext must be an associative array with key/value pairs to be replaced 
  949. public function replace_inline_vars( $text, $mod = false, $atts = array(), $extra = array() ) { 
  950.  
  951. if ( strpos( $text, '%%' ) === false ) { 
  952. if ( $this->p->debug->enabled ) 
  953. $this->p->debug->log( 'exiting early: no inline vars' ); 
  954. return $text; 
  955.  
  956. // $mod is preferred but not required 
  957. // $mod = true | false | post_id | $mod array 
  958. if ( ! is_array( $mod ) ) { 
  959. if ( $this->p->debug->enabled ) { 
  960. $this->p->debug->log( 'optional call to get_page_mod()' ); 
  961. $mod = $this->get_page_mod( $mod ); 
  962.  
  963. $vars = $this->get_inline_vars(); 
  964. $vals = $this->get_inline_vals( $mod, $atts ); 
  965.  
  966. if ( ! empty( $extra ) && self::is_assoc( $extra ) ) { 
  967. foreach ( $extra as $match => $replace ) { 
  968. $vars[] = '%%'.$match.'%%'; 
  969. $vals[] = $replace; 
  970.  
  971. ksort( $vars ); 
  972. ksort( $vals ); 
  973.  
  974. return str_replace( $vars, $vals, $text ); 
  975.  
  976. // use a reference to modify the $options array directly 
  977. // $keys can be a single key name or an array of key names 
  978. public function add_image_url_size( $keys, array &$opts ) { 
  979. if ( $this->p->debug->enabled ) 
  980. $this->p->debug->mark(); 
  981.  
  982. if ( ! is_array( $keys ) ) 
  983. $keys = array( $keys ); 
  984.  
  985. $lca = $this->p->cf['lca']; 
  986. $disabled = SucomUtil::get_const( 'NGFB_PHP_GETIMGSIZE_DISABLE' ); 
  987. $cache_exp = (int) apply_filters( $lca.'_cache_expire_image_url_size',  
  988. $this->p->options['plugin_imgsize_cache_exp'] ); 
  989.  
  990. foreach ( $keys as $prefix ) { 
  991.  
  992. $media_url = SucomUtil::get_mt_media_url( $opts, $prefix ); 
  993.  
  994. if ( ! $disabled && ! empty( $media_url ) && strpos( $media_url, '://' ) !== false ) { 
  995.  
  996. $cache_salt = __METHOD__.'(url:'.$media_url.')'; 
  997. $cache_id = $lca.'_'.md5( $cache_salt ); 
  998.  
  999. if ( $this->p->debug->enabled ) { 
  1000. $this->p->debug->log( 'transient cache salt '.$cache_salt ); 
  1001.  
  1002. if ( $cache_exp > 0 ) { 
  1003. $image_info = get_transient( $cache_id ); 
  1004. if ( is_array( $image_info ) ) { 
  1005. if ( $this->p->debug->enabled ) { 
  1006. $this->p->debug->log( 'image info for '.$media_url.' retrieved from transient' ); 
  1007. } else { 
  1008. $image_info = false; 
  1009. } else { 
  1010. if ( $this->p->debug->enabled ) { 
  1011. $this->p->debug->log( 'image info transient cache is disabled' ); 
  1012. $image_info = false; 
  1013.  
  1014. if ( $image_info === false ) { 
  1015.  
  1016. $image_info = @getimagesize( $media_url ); 
  1017.  
  1018. if ( is_array( $image_info ) ) { 
  1019. if ( $this->p->debug->enabled ) { 
  1020. $this->p->debug->log( 'PHP getimagesize() for '.$media_url.' returned '. 
  1021. $image_info[0].'x'.$image_info[1] ); 
  1022. if ( $cache_exp > 0 ) { 
  1023. set_transient( $cache_id, $image_info, $cache_exp ); 
  1024. if ( $this->p->debug->enabled ) { 
  1025. $this->p->debug->log( 'image url size saved to transient '. 
  1026. $cache_id.' ('.$cache_exp.' seconds)'); 
  1027. } elseif ( $this->p->debug->enabled ) { 
  1028. $this->p->debug->log( 'PHP getimagesize() did not return an array' ); 
  1029. $image_info = array( NGFB_UNDEF_INT, NGFB_UNDEF_INT, '', '' ); 
  1030.  
  1031. list( $opts[$prefix.':width'], $opts[$prefix.':height'], $image_type, $image_attr ) = $image_info; 
  1032.  
  1033. } else { 
  1034. foreach ( array( 'width', 'height' ) as $attr ) 
  1035. if ( isset( $opts[$prefix.':'.$attr] ) ) 
  1036. $opts[$prefix.':'.$attr] = NGFB_UNDEF_INT; 
  1037.  
  1038. return $opts; 
  1039.  
  1040. // accepts json script or json array 
  1041. public function json_format( $json, $options = 0, $depth = 32 ) { 
  1042.  
  1043. $do_pretty_print = self::get_const( 'NGFB_JSON_PRETTY_PRINT' ); 
  1044. $ext_json_disable = self::get_const( 'NGFB_EXT_JSON_DISABLE', false ); 
  1045. $do_ext_pretty = false; 
  1046.  
  1047. if ( $options === 0 && defined( 'JSON_UNESCAPED_SLASHES' ) ) { 
  1048. $options = JSON_UNESCAPED_SLASHES; // since PHP v5.4 
  1049.  
  1050. // decide if the encoded json will be minimized or not 
  1051. if ( is_admin() || $this->p->debug->enabled || $do_pretty_print ) { 
  1052. if ( defined( 'JSON_PRETTY_PRINT' ) ) { // since PHP v5.4 
  1053. $options = $options|JSON_PRETTY_PRINT; 
  1054. } else { 
  1055. $do_ext_pretty = true; // use the SuextJsonFormat lib 
  1056.  
  1057. // encode the json 
  1058. if ( ! is_string( $json ) ) 
  1059. $json = self::json_encode_array( $json, $options, $depth ); // prefers wp_json_encode() to json_encode() 
  1060.  
  1061. // use the pretty print external library for older PHP versions 
  1062. // define NGFB_EXT_JSON_DISABLE as true to prevent external json formatting  
  1063. if ( ! $ext_json_disable && $do_ext_pretty ) { 
  1064. $classname = NgfbConfig::load_lib( false, 'ext/json-format', 'suextjsonformat' ); 
  1065. if ( $classname !== false && class_exists( $classname ) ) 
  1066. $json = SuextJsonFormat::get( $json, $options, $depth ); 
  1067.  
  1068. return $json; 
  1069.  
  1070. /** 
  1071. * Determine and return the post/user/term module array. 
  1072. */ 
  1073. public function get_page_mod( $use_post = false, $mod = false, $wp_obj = false ) { 
  1074.  
  1075. if ( ! is_array( $mod ) ) { 
  1076. $mod = array(); 
  1077. } elseif ( isset( $mod['obj'] ) && is_object( $mod['obj'] ) ) { 
  1078. if ( $this->p->debug->enabled ) { 
  1079. $this->p->debug->log( 'exiting early: module object is defined' ); 
  1080. return $mod; 
  1081.  
  1082. if ( $this->p->debug->enabled ) { 
  1083. $this->p->debug->mark(); 
  1084.  
  1085. // check for a recognized object 
  1086. if ( is_object( $wp_obj ) ) { 
  1087. if ( $this->p->debug->enabled ) { 
  1088. $this->p->debug->log( 'wp_obj is '.get_class( $wp_obj ) ); 
  1089. switch ( get_class( $wp_obj ) ) { 
  1090. case 'WP_Post': 
  1091. $mod['name'] = 'post'; 
  1092. $mod['id'] = $wp_obj->ID; 
  1093. break; 
  1094. case 'WP_Term': 
  1095. $mod['name'] = 'term'; 
  1096. $mod['id'] = $wp_obj->term_id; 
  1097. break; 
  1098. case 'WP_User': 
  1099. $mod['name'] = 'user'; 
  1100. $mod['id'] = $wp_obj->ID; 
  1101. break; 
  1102.  
  1103. // we need a module name to get the id and object 
  1104. if ( empty( $mod['name'] ) ) { 
  1105. if ( self::is_post_page( $use_post ) ) { // $use_post = true | false | post_id  
  1106. $mod['name'] = 'post'; 
  1107. } elseif ( self::is_term_page() ) { 
  1108. $mod['name'] = 'term'; 
  1109. } elseif ( self::is_user_page() ) { 
  1110. $mod['name'] = 'user'; 
  1111. } else { 
  1112. $mod['name'] = false; 
  1113.  
  1114. if ( empty( $mod['id'] ) ) { 
  1115. if ( $mod['name'] === 'post' ) { 
  1116. $mod['id'] = self::get_post_object( $use_post, 'id' ); // $use_post = true | false | post_id  
  1117. } elseif ( $mod['name'] === 'term' ) { 
  1118. $mod['id'] = self::get_term_object( false, '', 'id' ); 
  1119. } elseif ( $mod['name'] === 'user' ) { 
  1120. $mod['id'] = self::get_user_object( false, 'id' ); 
  1121. } else { 
  1122. $mod['id'] = false; 
  1123.  
  1124. if ( isset( $this->p->m['util'][$mod['name']] ) ) { // make sure we have a complete $mod array 
  1125. $mod = $this->p->m['util'][$mod['name']]->get_mod( $mod['id'] ); 
  1126. } else { 
  1127. $mod = array_merge( NgfbMeta::$mod_defaults, $mod ); 
  1128.  
  1129. $mod['use_post'] = $use_post; 
  1130.  
  1131. /** 
  1132. * The post module defines is_home_page, is_home_index, and is_home. 
  1133. * If we don't have a module, then check if we're on the home index page. 
  1134. */ 
  1135. if ( $mod['name'] === false ) { 
  1136. $mod['is_home_index'] = $mod['is_home'] = is_home(); 
  1137.  
  1138. if ( $this->p->debug->enabled ) { 
  1139. $this->p->debug->log_arr( '$mod', $mod ); 
  1140.  
  1141. return $mod; 
  1142.  
  1143. /** 
  1144. * $mod is false when used for open graph meta tags and buttons in widget. 
  1145. * $mod is true when buttons are added to individual posts on an index webpage. 
  1146. */ 
  1147. public function get_sharing_url( $mod = false, $add_page = true, $src_id = '' ) { 
  1148. if ( $this->p->debug->enabled ) { 
  1149. $this->p->debug->mark(); 
  1150. return $this->get_page_url( 'sharing', $mod, $add_page, $src_id ); 
  1151.  
  1152. public function get_canonical_url( $mod = false, $add_page = true, $src_id = '' ) { 
  1153. if ( $this->p->debug->enabled ) { 
  1154. $this->p->debug->mark(); 
  1155. return $this->get_page_url( 'canonical', $mod, $add_page, $src_id ); 
  1156.  
  1157. private function get_page_url( $type, $mod, $add_page, $src_id ) { 
  1158.  
  1159. if ( $this->p->debug->enabled ) { 
  1160. $this->p->debug->log_args( array(  
  1161. 'type' => $type,  
  1162. 'mod' => $mod,  
  1163. 'add_page' => $add_page,  
  1164. 'src_id' => $src_id,  
  1165. ) ); 
  1166.  
  1167. $lca = $this->p->cf['lca']; 
  1168. $url = false; 
  1169.  
  1170. // $mod is preferred but not required 
  1171. // $mod = true | false | post_id | $mod array 
  1172. if ( ! is_array( $mod ) ) { 
  1173. if ( $this->p->debug->enabled ) { 
  1174. $this->p->debug->log( 'optional call to get_page_mod()' ); 
  1175. $mod = $this->get_page_mod( $mod ); 
  1176.  
  1177. if ( $mod['is_post'] ) { 
  1178. if ( ! empty( $mod['id'] ) ) { 
  1179.  
  1180. if ( ! empty( $mod['obj'] ) ) { 
  1181. // get_options() returns null if an index key is not found 
  1182. $url = $mod['obj']->get_options( $mod['id'], $type.'_url' ); 
  1183.  
  1184. if ( ! empty( $url ) ) { // must be a non-empty string 
  1185. if ( $this->p->debug->enabled ) { 
  1186. $this->p->debug->log( 'custom post '.$type.'_url = '.$url ); 
  1187. } else { 
  1188. $url = $this->check_url_string( get_permalink( $mod['id'] ), 'post permalink' ); 
  1189.  
  1190. if ( ! empty( $url ) && $add_page && get_query_var( 'page' ) > 1 ) { 
  1191. global $wp_rewrite; 
  1192. $post_obj = self::get_post_object( $mod['id'] ); 
  1193. $numpages = substr_count( $post_obj->post_content, '<!--nextpage-->' ) + 1; 
  1194.  
  1195. if ( $numpages && get_query_var( 'page' ) <= $numpages ) { 
  1196. if ( ! $wp_rewrite->using_permalinks() || strpos( $url, '?' ) !== false ) 
  1197. $url = add_query_arg( 'page', get_query_var( 'page' ), $url ); 
  1198. else $url = user_trailingslashit( trailingslashit( $url ).get_query_var( 'page' ) ); 
  1199. if ( $this->p->debug->enabled ) 
  1200. $this->p->debug->log( 'add page query url = '.$url ); 
  1201. $url = apply_filters( $lca.'_post_url', $url, $mod, $add_page, $src_id ); 
  1202.  
  1203. } else { 
  1204. if ( $mod['is_home'] ) { 
  1205. if ( get_option( 'show_on_front' ) === 'page' ) { // show_on_front = posts | page 
  1206. $url = $this->check_url_string( get_permalink( get_option( 'page_for_posts' ) ), 'page for posts' ); 
  1207. } else { 
  1208. $url = apply_filters( $lca.'_home_url', home_url( '/' ), $mod, $add_page, $src_id ); 
  1209. if ( $this->p->debug->enabled ) 
  1210. $this->p->debug->log( 'home url = '.$url ); 
  1211. } elseif ( $mod['is_term'] ) { 
  1212. if ( ! empty( $mod['id'] ) ) { 
  1213.  
  1214. if ( ! empty( $mod['obj'] ) ) { 
  1215. // get_options() returns null if an index key is not found 
  1216. $url = $mod['obj']->get_options( $mod['id'], $type.'_url' ); 
  1217.  
  1218. if ( ! empty( $url ) ) { // must be a non-empty string 
  1219. if ( $this->p->debug->enabled ) { 
  1220. $this->p->debug->log( 'custom term '.$type.'_url = '.$url ); 
  1221. } else { 
  1222. $url = $this->check_url_string( get_term_link( $mod['id'], $mod['tax_slug'] ), 'term link' ); 
  1223. }  
  1224. $url = apply_filters( $lca.'_term_url', $url, $mod, $add_page, $src_id ); 
  1225.  
  1226. } elseif ( $mod['is_user'] ) { 
  1227. if ( ! empty( $mod['id'] ) ) { 
  1228.  
  1229. if ( ! empty( $mod['obj'] ) ) { 
  1230. // get_options() returns null if an index key is not found 
  1231. $url = $mod['obj']->get_options( $mod['id'], $type.'_url' ); 
  1232.  
  1233. if ( ! empty( $url ) ) { // must be a non-empty string 
  1234. if ( $this->p->debug->enabled ) { 
  1235. $this->p->debug->log( 'custom user '.$type.'_url = '.$url ); 
  1236. } else { 
  1237. $url = $this->check_url_string( get_author_posts_url( $mod['id'] ), 'author posts' ); 
  1238. $url = apply_filters( $lca.'_user_url', $url, $mod, $add_page, $src_id ); 
  1239.  
  1240. } elseif ( is_search() ) { 
  1241. $url = $this->check_url_string( get_search_link(), 'search link' ); 
  1242. $url = apply_filters( $lca.'_search_url', $url, $mod, $add_page, $src_id ); 
  1243.  
  1244. } elseif ( function_exists( 'get_post_type_archive_link' ) && is_post_type_archive() ) { 
  1245. $url = $this->check_url_string( get_post_type_archive_link( get_query_var( 'post_type' ) ), 'post type archive' ); 
  1246.  
  1247. } elseif ( SucomUtil::is_archive_page() ) { 
  1248. if ( is_date() ) { 
  1249. if ( is_day() ) 
  1250. $url = $this->check_url_string( get_day_link( get_query_var( 'year' ),  
  1251. get_query_var( 'monthnum' ), get_query_var( 'day' ) ), 'day link' ); 
  1252. elseif ( is_month() ) 
  1253. $url = $this->check_url_string( get_month_link( get_query_var( 'year' ),  
  1254. get_query_var( 'monthnum' ) ), 'month link' ); 
  1255. elseif ( is_year() ) 
  1256. $url = $this->check_url_string( get_year_link( get_query_var( 'year' ) ),  
  1257. 'year link' ); 
  1258. $url = apply_filters( $lca.'_archive_url', $url, $mod, $add_page, $src_id ); 
  1259.  
  1260. if ( ! empty( $url ) && $add_page && get_query_var( 'paged' ) > 1 ) { 
  1261. global $wp_rewrite; 
  1262. if ( ! $wp_rewrite->using_permalinks() ) { 
  1263. $url = add_query_arg( 'paged', get_query_var( 'paged' ), $url ); 
  1264. } else { 
  1265. if ( $mod['is_home_page'] ) { // static home page (have post id) 
  1266. $base = $GLOBALS['wp_rewrite']->using_index_permalinks() ? 'index.php/' : '/'; 
  1267. $url = home_url( $base ); 
  1268. if ( $this->p->debug->enabled ) 
  1269. $this->p->debug->log( 'home_url for '.$base.' = '.$url ); 
  1270. $url = user_trailingslashit( trailingslashit( $url ). 
  1271. trailingslashit( $wp_rewrite->pagination_base ).get_query_var( 'paged' ) ); 
  1272. if ( $this->p->debug->enabled ) { 
  1273. $this->p->debug->log( 'add paged query url = '.$url ); 
  1274.  
  1275. // fallback for themes and plugins that don't use the standard wordpress functions/variables 
  1276. if ( empty ( $url ) ) { 
  1277. // strip out tracking query arguments by facebook, google, etc. 
  1278. $url = preg_replace( '/([\?&])(fb_action_ids|fb_action_types|fb_source|fb_aggregation_id|'. 
  1279. 'utm_source|utm_medium|utm_campaign|utm_term|gclid|pk_campaign|pk_kwd)=[^&]*&?/i',  
  1280. '$1', self::get_prot().'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'] ); 
  1281. if ( $this->p->debug->enabled ) { 
  1282. $this->p->debug->log( 'server request url = '.$url ); 
  1283.  
  1284. return apply_filters( $lca.'_'.$type.'_url', $url, $mod, $add_page, $src_id ); 
  1285.  
  1286. private function check_url_string( $url, $source ) { 
  1287. if ( is_string( $url ) ) { 
  1288. if ( $this->p->debug->enabled ) 
  1289. $this->p->debug->log( $source.' url = '.$url ); 
  1290. return $url; // stop here 
  1291. if ( $this->p->debug->enabled ) { 
  1292. $this->p->debug->log( $source.' url is '.gettype( $url ) ); 
  1293. if ( is_wp_error( $url ) ) 
  1294. $this->p->debug->log( $source.' url error: '.$url->get_error_message() ); 
  1295. return false; 
  1296.  
  1297. // used by NgfbMedia get_content_images() and get_attachment_image_src(). 
  1298. public function fix_relative_url( $url ) { 
  1299. if ( empty( $url ) ||  
  1300. strpos( $url, '://' ) !== false ) 
  1301. return $url; 
  1302.  
  1303. if ( $this->p->debug->enabled ) 
  1304. $this->p->debug->log( 'relative url found = '.$url ); 
  1305.  
  1306. if ( strpos( $url, '//' ) === 0 ) 
  1307. $url = self::get_prot().':'.$url; 
  1308. elseif ( strpos( $url, '/' ) === 0 )  
  1309. $url = home_url( $url ); 
  1310. else { 
  1311. $base = self::get_prot().'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']; 
  1312. if ( strpos( $base, '?' ) !== false ) { 
  1313. $base_parts = explode( '?', $base ); 
  1314. $base = reset( $base_parts ); 
  1315. $url = trailingslashit( $base, false ).$url; 
  1316.  
  1317. if ( $this->p->debug->enabled ) 
  1318. $this->p->debug->log( 'relative url fixed = '.$url ); 
  1319.  
  1320. return $url; 
  1321.  
  1322. public function clear_uniq_urls( $context = 'default' ) { 
  1323. $cleared = isset( $this->uniq_urls[$context] ) ? 
  1324. count( $this->uniq_urls[$context] ) : 0; 
  1325. $this->uniq_urls[$context] = array(); 
  1326. if ( $this->p->debug->enabled ) 
  1327. $this->p->debug->log( 'cleared uniq url cache for context '.$context );  
  1328. return $cleared; 
  1329.  
  1330. public function is_dupe_url( $url, $context = 'default' ) { 
  1331. return $this->is_uniq_url( $url, $context ) ? false : true; 
  1332.  
  1333. public function is_uniq_url( $url, $context = 'default' ) { 
  1334. if ( empty( $url ) )  
  1335. return false; 
  1336.  
  1337. // complete the url with a protocol name 
  1338. if ( strpos( $url, '//' ) === 0 ) 
  1339. $url = self::get_prot().'//'.$url; 
  1340.  
  1341. if ( $this->p->debug->enabled &&  
  1342. strpos( $url, '://' ) === false ) 
  1343. $this->p->debug->log( 'incomplete url given for context '.$context.': '.$url ); 
  1344.  
  1345. if ( ! isset( $this->uniq_urls[$context][$url] ) ) { 
  1346. $this->uniq_urls[$context][$url] = 1; 
  1347. return true; 
  1348. } else { 
  1349. if ( $this->p->debug->enabled ) 
  1350. $this->p->debug->log( 'duplicate url rejected for context '.$context.': '.$url );  
  1351. return false; 
  1352.  
  1353. public function is_maxed( &$arr, $num = 0 ) { 
  1354. if ( ! is_array( $arr ) )  
  1355. return false; 
  1356. if ( $num > 0 && count( $arr ) >= $num )  
  1357. return true; 
  1358. return false; 
  1359.  
  1360. public function push_max( &$dst, &$src, $num = 0 ) { 
  1361. if ( ! is_array( $dst ) ||  
  1362. ! is_array( $src ) )  
  1363. return false; 
  1364.  
  1365. // if the array is not empty, or contains some non-empty values, then push it 
  1366. if ( ! empty( $src ) &&  
  1367. array_filter( $src ) )  
  1368. array_push( $dst, $src ); 
  1369.  
  1370. return $this->slice_max( $dst, $num ); // returns true or false 
  1371.  
  1372. public function slice_max( &$arr, $num = 0 ) { 
  1373. if ( ! is_array( $arr ) ) 
  1374. return false; 
  1375.  
  1376. $has = count( $arr ); 
  1377.  
  1378. if ( $num > 0 ) { 
  1379. if ( $has == $num ) { 
  1380. if ( $this->p->debug->enabled ) 
  1381. $this->p->debug->log( 'max values reached ('.$has.' == '.$num.')' ); 
  1382. return true; 
  1383. } elseif ( $has > $num ) { 
  1384. if ( $this->p->debug->enabled ) 
  1385. $this->p->debug->log( 'max values reached ('.$has.' > '.$num.') - slicing array' ); 
  1386. $arr = array_slice( $arr, 0, $num ); 
  1387. return true; 
  1388.  
  1389. return false; 
  1390.  
  1391. // get maximum media values from custom meta or plugin settings 
  1392. public function get_max_nums( array &$mod, $opt_pre = 'og' ) { 
  1393. $max = array(); 
  1394. $opt_keys = array( $opt_pre.'_vid_max', $opt_pre.'_img_max' ); 
  1395.  
  1396. foreach ( $opt_keys as $max_key ) { 
  1397.  
  1398. if ( ! empty( $mod['id'] ) && ! empty( $mod['obj'] ) ) { 
  1399. // get_options() returns null if an index key is not found 
  1400. $max_val = $mod['obj']->get_options( $mod['id'], $max_key ); 
  1401. } else { 
  1402. $max_val = null; // default value if index key is missing 
  1403.  
  1404. // quick sanitation of returned value - ignore NGFB_UNDEF_INT values 
  1405. if ( $max_val !== null & is_numeric( $max_val ) && $max_val >= 0 ) { 
  1406. $max[$max_key] = $max_val; 
  1407. if ( $this->p->debug->enabled ) { 
  1408. $this->p->debug->log( 'found custom meta '.$max_key.' = '.$max_val ); 
  1409. } else { 
  1410. $max[$max_key] = isset( $this->p->options[$max_key] ) ? // fallback to options 
  1411. $this->p->options[$max_key] : 0; 
  1412.  
  1413. return $max; 
  1414.  
  1415. public function get_admin_url( $menu_id = '', $link_text = '', $menu_lib = '' ) { 
  1416.  
  1417. $hash = ''; 
  1418. $query = ''; 
  1419. $admin_url = ''; 
  1420. $lca = $this->p->cf['lca']; 
  1421.  
  1422. // $menu_id may start with a hash or query, so parse before checking its value 
  1423. if ( strpos( $menu_id, '#' ) !== false ) 
  1424. list( $menu_id, $hash ) = explode( '#', $menu_id ); 
  1425.  
  1426. if ( strpos( $menu_id, '?' ) !== false ) 
  1427. list( $menu_id, $query ) = explode( '?', $menu_id ); 
  1428.  
  1429. if ( empty( $menu_id ) ) { 
  1430. $current = $_SERVER['REQUEST_URI']; 
  1431. if ( preg_match( '/^.*\?page='.$lca.'-([^&]*).*$/', $current, $match ) ) 
  1432. $menu_id = $match[1]; 
  1433. else $menu_id = key( $this->p->cf['*']['lib']['submenu'] ); // default to first submenu 
  1434.  
  1435. // find the menu_lib value for this menu_id 
  1436. if ( empty( $menu_lib ) ) { 
  1437. foreach ( $this->p->cf['*']['lib'] as $menu_lib => $menu ) { 
  1438. if ( isset( $menu[$menu_id] ) ) 
  1439. break; 
  1440. else $menu_lib = ''; 
  1441.  
  1442. if ( empty( $menu_lib ) || 
  1443. empty( $this->p->cf['wp']['admin'][$menu_lib]['page'] ) ) 
  1444. return; 
  1445.  
  1446. $parent_slug = $this->p->cf['wp']['admin'][$menu_lib]['page'].'?page='.$lca.'-'.$menu_id; 
  1447.  
  1448. switch ( $menu_lib ) { 
  1449. case 'sitesubmenu': 
  1450. $admin_url = network_admin_url( $parent_slug ); 
  1451. break; 
  1452. default: 
  1453. $admin_url = admin_url( $parent_slug ); 
  1454. break; 
  1455.  
  1456. if ( ! empty( $query ) )  
  1457. $admin_url .= '&'.$query; 
  1458.  
  1459. if ( ! empty( $hash ) )  
  1460. $admin_url .= '#'.$hash; 
  1461.  
  1462. if ( empty( $link_text ) ) { 
  1463. return $admin_url; 
  1464. } else { 
  1465. return '<a href="'.$admin_url.'">'.$link_text.'</a>'; 
  1466.  
  1467. public function do_metabox_tabs( $metabox = '', $tabs = array(), $table_rows = array(), $args = array() ) { 
  1468.  
  1469. $tab_keys = array_keys( $tabs ); 
  1470. $default_tab = '_'.reset( $tab_keys ); // must start with an underscore 
  1471. $class_metabox_tabs = 'sucom-metabox-tabs'; 
  1472. $class_link = 'sucom-tablink'; 
  1473. $class_tabset = 'sucom-tabset'; 
  1474.  
  1475. if ( ! empty( $metabox ) ) { 
  1476. $metabox = '_'.$metabox; // must start with an underscore 
  1477. $class_metabox_tabs .= ' '.$class_metabox_tabs.$metabox; 
  1478. $class_link .= ' '.$class_link.$metabox; 
  1479.  
  1480. // allow a css ID to be passed as a query argument 
  1481. extract( array_merge( array( 
  1482. 'scroll_to' => isset( $_GET['scroll_to'] ) ?  
  1483. '#'.self::sanitize_key( $_GET['scroll_to'] ) : '',  
  1484. ), $args ) ); 
  1485.  
  1486. echo "\n".'<script type="text/javascript">jQuery(document).ready(function() { '. 
  1487. 'sucomTabs(\''.$metabox.'\', \''.$default_tab.'\', \''.$scroll_to.'\'); });</script>'."\n"; 
  1488. echo '<div class="'.$class_metabox_tabs.'">'."\n"; 
  1489. echo '<ul class="'.$class_metabox_tabs.'">'."\n"; 
  1490. foreach ( $tabs as $tab => $title ) { 
  1491. $class_href_key = $class_tabset.$metabox.'-tab_'.$tab; 
  1492. echo '<div class="tab_left"> </div><li class="'. 
  1493. $class_href_key.'"><a class="'.$class_link.'" href="#'. 
  1494. $class_href_key.'">'.$title.'</a></li>'."\n"; 
  1495. echo '</ul><!-- .'.$class_metabox_tabs.' -->'."\n"; 
  1496.  
  1497. foreach ( $tabs as $tab => $title ) { 
  1498. $class_href_key = $class_tabset.$metabox.'-tab_'.$tab; 
  1499. $this->do_table_rows( $table_rows[$tab], $class_href_key, ( empty( $metabox ) ? 
  1500. '' : $class_tabset.$metabox ), $class_tabset ); 
  1501. echo '</div><!-- .'.$class_metabox_tabs.' -->'."\n\n"; 
  1502.  
  1503. public function do_table_rows( $table_rows, $class_href_key = '', $class_tabset_mb = '', $class_tabset = '' ) { 
  1504.  
  1505. if ( ! is_array( $table_rows ) ) // just in case 
  1506. return; 
  1507.  
  1508. $lca = empty( $this->p->cf['lca'] ) ?  
  1509. 'sucom' : $this->p->cf['lca']; 
  1510. $total_rows = count( $table_rows ); 
  1511. $count_rows = 0; 
  1512. $hidden_opts = 0; 
  1513. $hidden_rows = 0; 
  1514.  
  1515. // use call_user_func() instead of $classname::show_opts() for PHP 5.2 
  1516. $show_opts = class_exists( $lca.'user' ) ?  
  1517. call_user_func( array( $lca.'user', 'show_opts' ) ) : 'basic'; 
  1518.  
  1519. foreach ( $table_rows as $key => $row ) { 
  1520. if ( empty( $row ) ) // just in case 
  1521. continue; 
  1522.  
  1523. // default row class and id attribute values 
  1524. $tr = array( 
  1525. 'class' => 'sucom_alt'.( $count_rows % 2 ). 
  1526. ( $count_rows === 0 ? ' first_row' : '' ). 
  1527. ( $count_rows === ( $total_rows - 1 ) ? ' last_row' : '' ),  
  1528. 'id' => ( is_int( $key ) ? '' : 'tr_'.$key ) 
  1529. ); 
  1530.  
  1531. // if we don't already have a table row tag, then add one 
  1532. if ( strpos( $row, '<tr ' ) === false ) { 
  1533. $row = '<tr class="'.$tr['class'].'"'. 
  1534. ( empty( $tr['id'] ) ? '' : ' id="'.$tr['id'].'"' ).'>'.$row; 
  1535. } else { 
  1536. foreach ( $tr as $att => $val ) { 
  1537. if ( empty( $tr[$att] ) ) 
  1538. continue; 
  1539.  
  1540. // if we're here, then we have a table row tag already 
  1541. // count the number of rows and options that are hidden 
  1542. if ( $att === 'class' && ! empty( $show_opts ) &&  
  1543. ( $matched = preg_match( '/<tr [^>]*class="[^"]*hide(_row)?_in_'.$show_opts.'[" ]/', $row, $m ) > 0 ) ) { 
  1544.  
  1545. if ( ! isset( $m[1] ) ) { 
  1546. $hidden_opts += preg_match_all( '/(<th|<tr[^>]*><td)/', $row, $all_matches ); 
  1547.  
  1548. $hidden_rows += $matched; 
  1549.  
  1550. // add the attribute value 
  1551. $row = preg_replace( '/(<tr [^>]*'.$att.'=")([^"]*)(")/', '$1$2 '.$tr[$att].'$3', $row, -1, $cnt ); 
  1552.  
  1553. // if one hasn't been added, then add both the attribute and its value 
  1554. if ( $cnt < 1 ) { 
  1555. $row = preg_replace( '/(<tr )/', '$1'.$att.'="'.$tr[$att].'" ', $row, -1, $cnt ); 
  1556.  
  1557. // add a closing table row tag if we don't already have one 
  1558. if ( strpos( $row, '</tr>' ) === false ) 
  1559. $row .= '</tr>'."\n"; 
  1560.  
  1561. // update the table row array element with the new value 
  1562. $table_rows[$key] = $row; 
  1563.  
  1564. $count_rows++; 
  1565.  
  1566. if ( $count_rows === 0 ) { 
  1567. $table_rows[] = '<tr><td align="center"><p><em>'. 
  1568. __( 'No options available.', 'nextgen-facebook' ). 
  1569. '</em></p></td></tr>'; 
  1570. $count_rows++; 
  1571.  
  1572. echo '<div class="'. 
  1573. ( empty( $show_opts ) ? '' : 'sucom-show_'.$show_opts ). 
  1574. ( empty( $class_tabset ) ? '' : ' '.$class_tabset ). 
  1575. ( empty( $class_tabset_mb ) ? '' : ' '.$class_tabset_mb ). 
  1576. ( empty( $class_href_key ) ? '' : ' '.$class_href_key ). 
  1577. '">'."\n"; 
  1578.  
  1579. echo '<table class="sucom-settings '.$lca. 
  1580. ( empty( $class_href_key ) ? '' : ' '.$class_href_key ). 
  1581. ( $hidden_rows > 0 && $hidden_rows === $count_rows ? // if all rows hidden, then hide the whole table 
  1582. ' hide_in_'.$show_opts : '' ).'">'."\n"; 
  1583.  
  1584. foreach ( $table_rows as $row ) 
  1585. echo $row; 
  1586.  
  1587. echo '</table>'."\n"; 
  1588. echo '</div>'."\n"; 
  1589.  
  1590. $show_opts_label = $this->p->cf['form']['show_options'][$show_opts]; 
  1591. if ( $hidden_opts > 0 ) { 
  1592. echo '<div class="hidden_opts_msg '.$class_tabset.'-msg '.$class_tabset_mb.'-msg '.$class_href_key.'-msg">'. 
  1593. sprintf( _x( '%1$d additional options not shown in %2$s view', 'option comment', 'nextgen-facebook' ),  
  1594. $hidden_opts, _x( $show_opts_label, 'option value', 'nextgen-facebook' ) ). 
  1595. ' (<a href="javascript:void(0);"'. 
  1596. ' onClick="sucomViewUnhideRows( \''.$class_href_key.'\', \''.$show_opts.'\' );">'. 
  1597. _x( 'unhide these options', 'option comment', 'nextgen-facebook' ).'</a>)</div>'."\n"; 
  1598. } elseif ( $hidden_rows > 0 ) { 
  1599. echo '<div class="hidden_opts_msg '.$class_tabset.'-msg '.$class_tabset_mb.'-msg '.$class_href_key.'-msg">'. 
  1600. sprintf( _x( '%1$d additional rows not shown in %2$s view', 'option comment', 'nextgen-facebook' ),  
  1601. $hidden_rows, _x( $show_opts_label, 'option value', 'nextgen-facebook' ) ). 
  1602. ' (<a href="javascript:void(0);"'. 
  1603. ' onClick="sucomViewUnhideRows( \''.$class_href_key.'\', \''.$show_opts.'\', \'hide_row_in\' );">'. 
  1604. _x( 'unhide these rows', 'option comment', 'nextgen-facebook' ).'</a>)</div>'."\n"; 
  1605.  
  1606. public function shorten_html_href( $html ) { 
  1607. return preg_replace_callback( '/(href=[\'"])([^\'"]+)([\'"])/',  
  1608. array( &$this, 'shorten_html_href_value' ), $html ); 
  1609.  
  1610. private function shorten_html_href_value( $matches ) { 
  1611. if ( $this->p->debug->enabled ) 
  1612. $this->p->debug->log( 'shortening url '.$matches[2] ); 
  1613. return $matches[1].apply_filters( $this->p->cf['lca'].'_shorten_url',  
  1614. $matches[2], $this->p->options['plugin_shortener'] ).$matches[3]; 
  1615.  
  1616. public function rename_opts_by_ext( &$opts, $options_keys ) { 
  1617.  
  1618. foreach ( $this->p->cf['plugin'] as $ext => $info ) { 
  1619.  
  1620. if ( ! isset( $options_keys[$ext] ) || is_array( $options_keys[$ext] ) ) { 
  1621. continue; 
  1622. } elseif ( ! isset( $info['opt_version'] ) ) { // just in case 
  1623. continue; 
  1624. } elseif ( empty( $opts['plugin_'.$ext.'_opt_version'] ) ) { // no upgrade required 
  1625. continue; 
  1626. }  
  1627.  
  1628. foreach ( $options_keys[$ext] as $max_version => $keys ) { 
  1629. if ( is_numeric( $max_version ) && is_array( $keys ) && 
  1630. $opts['plugin_'.$ext.'_opt_version'] <= $max_version ) { 
  1631.  
  1632. SucomUtil::rename_keys( $opts, $keys, true ); // rename $modifiers = true 
  1633.  
  1634. $opts['plugin_'.$ext.'_opt_version'] = $info['opt_version']; // mark as current 
  1635. $opts['options_version'] = $this->p->cf['opt']['version']; // mark as current 
  1636.  
  1637. // limit_text_length() uses PHP's multibyte functions (mb_strlen and mb_substr) for UTF8 
  1638. public function limit_text_length( $text, $maxlen = 300, $trailing = '', $cleanup_html = true ) { 
  1639.  
  1640. if ( $cleanup_html === true ) { 
  1641. $text = $this->cleanup_html_tags( $text ); // remove any remaining html tags 
  1642.  
  1643. $charset = get_bloginfo( 'charset' ); 
  1644. $text = html_entity_decode( self::decode_utf8( $text ), ENT_QUOTES, $charset ); 
  1645.  
  1646. if ( $maxlen > 0 ) { 
  1647. if ( mb_strlen( $trailing ) > $maxlen ) 
  1648. $trailing = mb_substr( $trailing, 0, $maxlen ); // trim the trailing string, if too long 
  1649. if ( mb_strlen( $text ) > $maxlen ) { 
  1650. $text = mb_substr( $text, 0, $maxlen - mb_strlen( $trailing ) ); 
  1651. $text = trim( preg_replace( '/[^ ]*$/', '', $text ) ); // remove trailing bits of words 
  1652. $text = preg_replace( '/[, \.]*$/', '', $text ); // remove trailing puntuation 
  1653. } else $trailing = ''; // truncate trailing string if text is less than maxlen 
  1654. $text = $text.$trailing; // trim and add trailing string (if provided) 
  1655.  
  1656. $text = preg_replace( '/ /', ' ', $text); // just in case 
  1657.  
  1658. return $text; 
  1659.  
  1660. public function cleanup_html_tags( $text, $strip_tags = true, $use_img_alt = false ) { 
  1661. $alt_text = ''; 
  1662. $alt_prefix = isset( $this->p->options['plugin_img_alt_prefix'] ) ? 
  1663. $this->p->options['plugin_img_alt_prefix'] : 'Image:'; 
  1664.  
  1665. $text = self::strip_shortcodes( $text ); // remove any remaining shortcodes 
  1666. $text = preg_replace( '/[\s\n\r]+/s', ' ', $text ); // put everything on one line 
  1667. $text = preg_replace( '/<\?.*\?'.'>/U', ' ', $text); // remove php 
  1668. $text = preg_replace( '/<script\b[^>]*>(.*)<\/script>/Ui', ' ', $text); // remove javascript 
  1669. $text = preg_replace( '/<style\b[^>]*>(.*)<\/style>/Ui', ' ', $text); // remove inline stylesheets 
  1670.  
  1671. $text = preg_replace( '/<!--'.$this->p->cf['lca'].'-ignore-->(.*?)<!--\/'. 
  1672. $this->p->cf['lca'].'-ignore-->/Ui', ' ', $text); // remove text between comment strings 
  1673.  
  1674. if ( $strip_tags ) { 
  1675. $text = preg_replace( '/<\/p>/i', ' ', $text); // replace end of paragraph with a space 
  1676. $text_stripped = trim( strip_tags( $text ) ); // remove remaining html tags 
  1677.  
  1678. if ( $text_stripped === '' && $use_img_alt ) { // possibly use img alt strings if no text 
  1679. if ( strpos( $text, '<img ' ) !== false && 
  1680. preg_match_all( '/<img [^>]*alt=["\']([^"\'>]*)["\']/Ui',  
  1681. $text, $all_matches, PREG_PATTERN_ORDER ) ) { 
  1682.  
  1683. foreach ( $all_matches[1] as $alt ) { 
  1684. $alt = trim( $alt ); 
  1685. if ( ! empty( $alt ) ) { 
  1686. $alt = empty( $alt_prefix ) ?  
  1687. $alt : $alt_prefix.' '.$alt; 
  1688.  
  1689. // add a period after the image alt text if missing 
  1690. $alt_text .= ( strpos( $alt, '.' ) + 1 ) === strlen( $alt ) ?  
  1691. $alt.' ' : $alt.'. '; 
  1692. if ( $this->p->debug->enabled ) 
  1693. $this->p->debug->log( 'img alt text: '.$alt_text ); 
  1694. $text = $alt_text; 
  1695. } else $text = $text_stripped; 
  1696.  
  1697. $text = preg_replace( '/(\xC2\xA0|\s)+/s', ' ', $text ); // replace 1+ spaces to a single space 
  1698.  
  1699. return trim( $text ); 
  1700.  
  1701.  
  1702. ?> 
.