Plugin Directory

Changeset 1962318


Ignore:
Timestamp:
10/24/2018 07:05:57 PM (7 years ago)
Author:
somatic
Message:

tweaks for PHP 7.2 compatibility

Location:
somatic-framework/trunk
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • somatic-framework/trunk/inc/somaFunctions.php

    r1600892 r1962318  
    33class somaFunctions extends somaticFramework {
    44
    5     function __construct() {
    6         add_action( 'init', array(__CLASS__,'init' ));
    7         add_action( 'admin_init', array(__CLASS__,'check_plugin_dependency' ));
    8         add_filter( 'query_vars', array(__CLASS__,'custom_query_vars' ));
    9         add_filter( 'parse_query', array(__CLASS__,'filter_current_query' ));           // empty at the moment
    10         add_filter( 'pre_get_posts', array(__CLASS__,'pre_get_posts'));
    11         add_action( 'before_delete_post', array(__CLASS__, 'delete_attachments_when_parents_die' ));        // have to do this "before", as wp_delete_post detaches all attachments before the 'delete_post' action, which left us unable to retrieve connected attachments...
    12         add_filter( 'gettext',  array(__CLASS__, 'modify_core_language'  ), 20, 3);
    13         add_filter( 'ngettext',  array(__CLASS__, 'modify_core_language'  ), 20, 3);
    14         // add_filter( 'login_redirect', array(__CLASS__, 'dashboard_redirect' ));
    15         add_filter( 'add_menu_classes', array(__CLASS__, 'show_pending_number'));
    16         // add_filter( 'wp_die_handler', array(__CLASS__, 'soma_wp_die_handler'),10,3);
    17         add_filter( 'editable_roles', array(__CLASS__, 'editable_roles'));
    18         add_filter( 'map_meta_cap', array(__CLASS__, 'admin_map_meta_cap'), 10, 4);
    19         remove_filter('check_comment_flood', 'check_comment_flood_db');                 // deal with "posting too quickly" problem....
    20         add_filter( 'edit_posts_per_page', array(__CLASS__, 'edit_list_length'));
    21         add_action( 'wp_ajax_unlink_file', array(__CLASS__, 'ajax_unlink_file'));
    22         add_action( 'wp_ajax_delete_attachment', array(__CLASS__, 'ajax_delete_attachment'));
    23         add_action( 'admin_notices', array(__CLASS__,'soma_admin_notices'), 100);
    24     }
    25 
    26     function init() {
    27         // localization init needed?? no translations yet
    28         // load_plugin_textdomain( 'stock-media-asset-manager', SOMA_DIR . '/lang', basename( dirname( __FILE__ ) ) . '/lang' );
    29         // self::session_manager(); /// WARNING this occasionally causes PHP errors: "Cannot send session cache limiter - headers already sent" -- do we need this?
    30         self::wp_version_check();
    31         self::check_staff();
    32     }
    33 
    34     // displays notices using the core wp messge box.
    35     function soma_admin_notices() {
    36         // if (soma_fetch_index($_GET['soma-notify']) == true) {
    37         // if (get_query_var('soma_notify') == true) {
    38         $notices = get_transient( 'soma_notices' );                     // check if any waiting
    39             if ($notices === false) return;
    40             foreach ($notices as $notice) {
    41                 if (empty($notice['msg'])) continue;
    42                 if (empty($notice['type'])) $notice['type'] == 'updated';
    43                 echo "<div class='{$notice['type']}'>";
    44                 echo "<p>{$notice['msg']}</p>";
    45                 echo "</div>";
    46             }
    47         // }
    48         delete_transient('soma_notices');
    49     }
    50 
    51     // adds an admin notice message to the queue. can use 'updated', or 'error'
    52     function queue_notice($type = 'updated', $msg) {
    53         $notices = get_transient( 'soma_notices' );                         // grab any existing notices
    54         if ($notices === false || !is_array($notices)) $notices = array();  // or init
    55         $notices[] = array('type' => $type, 'msg' => $msg);                 // add to the queue
    56         set_transient( 'soma_notices', $notices );                          // store it
    57     }
    58 
    59     // adds an admin notice as well as reloads the page to display the notice. good for returning the result of a custom manipulation function without leaving the page
    60     function completion_notice($type, $msg) {
    61         somaFunctions::queue_notice($type, $msg);
    62         // $result = add_query_arg( 'soma_notify', 'true', $_SERVER['HTTP_REFERER'] );      // used to be needed to trigger, but now we just check for the transient itself
    63         // wp_redirect( $result );
    64         wp_redirect( $_SERVER['HTTP_REFERER'] );
    65         exit;   // this halts everything, so this better be your last function call
    66     }
    67 
    68     // set if user has staff privileges (default to editors)
    69     function check_staff() {
    70         $privs = 'edit_others_posts';
    71         $privs = apply_filters('soma_staff_privs', $privs);
    72         define( 'SOMA_STAFF', current_user_can( $privs ));
    73     }
    74 
    75     // changes how many items are shown per page in /wp-admin/edit.php
    76     function edit_list_length() {
    77         return 40;
    78     }
    79 
    80     // checks if something is truly empty (not set) or null, and not simply set to a valid but negative value, like false or - 0 (0 as an integer) - 0.0 (0 as a float) - "0" (0 as a string)
    81     // NOTE: THIS DOESN'T AVOID THE PHP NOTICE ERROR IF SOMETHING DOESN'T EXIST (not set)
    82     function is_blank( $value ) {
    83         return empty( $value ) && !is_numeric( $value ) && $value !== false;
    84     }
    85 
    86     // checks if array is associative or not
    87     function array_is_associative($arr) {
    88         if (!is_array($arr)) return false;
    89         return array_keys($arr) !== range(0, count($arr) - 1);
    90     }
    91 
    92     // returns URL to facebook image from facebook ID stored in post_meta
    93     function fetch_facebook_pic($pid, $size = "square") {
    94         // sizes: square, small, normal, large
    95 
    96         $fid = somaFunctions::asset_meta('get', $pid, 'facebook_id');
    97         if (!$fid) return SOMA_IMG . '/generic-female-thumb.png';
    98 
    99         // $userurl = "http://graph.facebook.com/$fid";
    100         // $user = json_decode(file_get_contents($userurl));
    101         // $feedurl = "http://graph.facebook.com/$fid/feed";
    102         // $feed = json_decode(file_get_contents($feedurl));
    103         return "https://graph.facebook.com/$fid/picture?type=$size";
    104     }
    105 
    106     // since our postmeta is serialized, to query posts by postmeta requires retrieving a set of posts and then checking each entry against the desired postmeta for matches
    107     function fetch_posts($args, $meta_key = null, $meta_value = null) {
    108         if (!is_array($args)) return null;
    109         $args['numberposts'] = -1;  // always retrieve all, not just first 5
    110         $items = get_posts($args);
    111         $matches = array();
    112         foreach ($items as $item) {
    113             $val = somaFunctions::asset_meta('get', $item->ID, $meta_key);
    114             if ($val == $meta_value) {
    115                 $matches[] = $item;
    116             }
    117         }
    118         return $matches;
    119     }
    120 
    121     // retrieves array of user objects for a given role name
    122     function get_role_members_OLD($rolename) {
    123         $blogusers = get_users_of_blog();
    124         $members = array();
    125         foreach ($blogusers as $user) {
    126             $userdata = get_userdata($user->ID);
    127             // check role name
    128             if ($userdata->wp_capabilities[$rolename] == '1') {
    129                 // add to new role array
    130                 $members[] = $userdata;
    131             }
    132         }
    133         return $members;
    134     }
    135 
    136 
    137     // retrieves array of user objects for a given role name
    138     function get_role_members($roles = null) {
    139         if (!$roles) return false;
    140         // convert to array if single
    141         if (!is_array($roles)) {
    142             $roles = array($roles);
    143         }
    144         // init output array
    145         $members = array();
    146         global $wp_roles;
    147         foreach ($roles as $role) {
    148             if (!array_key_exists($role, $wp_roles->roles)) continue;   // abort if given a non-existant role (otherwise the user_query will return everyone)
    149             $wp_user_search = new WP_User_Query( array( 'role' => $role) );
    150             // wp_die(var_dump($wp_user_search));
    151             $users = $wp_user_search->get_results();
    152             if (empty($users)) continue;
    153             foreach ($users as $user) {
    154                 $members[] = $user;
    155             }
    156         }
    157         return $members;
    158     }
    159 
    160     // retrieves name of role assigned to pass user id or current user if not provided
    161     function get_user_role( $uid = null ) {
    162         if (!$uid) {
    163             $user = wp_get_current_user();
    164         } else {
    165             $user = new WP_User( $uid );
    166         }
    167         if ( !empty( $user->roles ) && is_array( $user->roles ) ) {
    168             foreach ( $user->roles as $role )
    169                 return $role;
    170         }
    171     }
    172 
    173     // comparison function for usort - allows sorting of arrays of objects by object properties
    174     function usort_displaynames($a, $b) {
    175         if ($a->display_name == $b->display_name)
    176             return 0;
    177         else
    178             return ($a->display_name < $b->display_name ? -1 : 1);
    179     }
    180 
    181     // comparison function for usort - allows sorting of arrays of objects by object properties
    182     function usort_ids($a, $b) {
    183         if ($a->ID == $b->ID)
    184             return 0;
    185         else
    186             return ($a->ID < $b->ID ? -1 : 1);
    187     }
    188 
    189     // checks to see if $_GET or $_POST values are set, avoids Undefined index error
    190     function fetch_index($array, $index) {
    191         if (!is_array($array)) return null;
    192         return isset($array[$index]) ? $array[$index] : null;
    193     }
    194 
    195     //
    196     function fetch_author($author_id = null, $output = "display_name") {
    197         if ($author_id != null) {
    198             $user = get_userdata($author_id);
    199             if ($user == null) {
    200                 return new WP_Error('missing','can\'t find the user...');
    201             }
    202         } else {
    203             return new WP_Error('missing','must provide author id...');
    204         }
    205         // $code = get_the_author_meta( somaMetaboxes::$meta_prefix .'user_code', $post->post_author );
    206         // $code = get_the_author_meta( 'user_exclusive', $post->post_author );
    207 
    208         if ($output == "link") {
    209             if (is_admin()) {
    210                 $url = admin_url() . "edit.php?author=" . $user->ID;
    211                 $url .=  $type ? "&post_type=".$type : "";
    212             } else {
    213                 $url = home_url() . "?author=" . $user->ID;
    214                 $url .=  $type ? "&post_type=" . $type : "";
    215             }
    216             return "<a href=\"" . $url . "\">". $user->display_name . "</a>";
    217         }
    218         return $user->$output;
    219     }
    220 
    221     //
    222     function fetch_post_author($post_id = null, $output = "display_name" ) {
    223         if (!$post_id) {
    224             global $post;
    225             if ($post == null) {
    226                 return new WP_Error('missing','can\'t find the post...');
    227             }
    228         } else {
    229             $post = get_post($post_id);
    230         }
    231         $type = $post->post_type;
    232         $user = get_userdata($post->post_author);
    233 
    234         // $code = get_the_author_meta( somaMetaboxes::$meta_prefix .'user_code', $post->post_author );
    235         // $code = get_the_author_meta( 'user_exclusive', $post->post_author );
    236 
    237         if ($output == "link") {
    238             if (is_admin()) {
    239                 $url = admin_url() . "edit.php?author=" . $user->ID;
    240                 $url .=  $type ? "&post_type=".$type : "";
    241             } else {
    242                 $url = home_url() . "?author=" . $user->ID;
    243                 $url .=  $type ? "&post_type=" . $type : "";
    244             }
    245             return "<a href=\"" . $url . "\">". $user->display_name . "</a>";
    246         }
    247         return $user->$output;
    248     }
    249 
    250     //
    251     function fetch_current_user($output = "display_name") {
    252         global $current_user;
    253         if ($current_user == null) {
    254             return new WP_Error('missing','can\'t find the user...');
    255         }
    256 
    257         if ($output == "link") {
    258             if (is_admin()) {
    259                 $url = admin_url() . "edit.php?author=" . $current_user->ID;
    260                 $url .=  $type ? "&post_type=".$type : "";
    261             } else {
    262                 $url = home_url() . "?author=" . $current_user->ID;
    263                 $url .=  $type ? "&post_type=" . $type : "";
    264             }
    265             return "<a href=\"" . $url . "\">". $current_user->display_name . "</a>";
    266         }
    267         return $current_user->$output;
    268     }
    269 
    270 
    271     //** clone of core get_the_term_list() - modifies the URL output if inside admin to stay in admin and to link to the edit post listing screen, also - can output comma separated string instead of links
    272     function fetch_the_term_list( $id = null, $taxonomy, $before = '', $sep = ', ', $after = '', $output = 'html' ) {
    273         if (is_null($id)) return false;
    274         $terms = wp_get_object_terms( $id, $taxonomy );
    275         $type = get_post_type($id);
    276         if ( is_wp_error( $terms ) )
    277             return $terms->get_error_message();
    278         if ( empty( $terms ) )
    279             return false;
    280         // create array with links
    281         if ($output == "html") {
    282             foreach ( $terms as $term ) {
    283                 // within admin, link to the admin edit post list screen, but filtering for this taxonomy term
    284                 if (is_admin()) {
    285                     $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term->slug . "&post_type=" . $type;
    286                 } else {
    287                     $link = get_term_link( $term, $taxonomy );
    288                 }
    289                 if ( is_wp_error( $link ) )
    290                     return $link->get_error_message();
    291                 $term_links[] = '<a href="' . $link . '" rel="tag">' . $term->name . '</a>';
    292             }
    293         // create plain string of comma-separated names
    294         }
    295         if ($output == "text") {
    296             foreach ($terms as $term) {
    297                 $term_links[] = $term->name;
    298             }
    299         }
    300 
    301         // outputs a vertical stack table for items in the edit.php listing
    302         if ($output == "table") {
    303             if (!is_admin()) return false;
    304 
    305             $table = "<table>";
    306             $i = 1;
    307             $max = count($terms);
    308             foreach ($terms as $term) {
    309                 // avoid border on last entry
    310                 if ($i == $max) {
    311                     $table .= "<tr><td class=\"last\">";
    312                 } else {
    313                     $table .= "<tr><td>";
    314                 }
    315                 $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term->slug . "&post_type=" . $type;
    316                 $table .= "<a href=\"".$link."\" rel=\"tag\">$term->name</a></td></tr>";
    317                 $i++;
    318             }
    319             $table .= "</table>";
    320             return $table;
    321         }
    322 
    323         return $before . join( $sep, $term_links ) . $after;
    324     }
    325 
    326     // retrieves taxonomy terms that are used in a singular way (only one possible state, ie ON or OFF)
    327     function fetch_the_singular_term( $post, $taxonomy, $label = "slug" ) {
    328         if (is_wp_error($post)) return $post;
    329         if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
    330         if (is_object($post)) {
    331             $pid = $post->ID;
    332         } else {
    333             $pid = intval($post);
    334         }
    335 
    336         $term = wp_get_object_terms( $pid, $taxonomy );
    337         if (is_wp_error($term) || empty($term)) return null;            // taxonomy doesn't exist OR there are no terms assigned
    338         if ($label == 'slug') {
    339             return $term[0]->slug;
    340         }
    341         if ($label == 'name') {
    342             return $term[0]->name;
    343         }
    344         if ($label == 'link') {
    345             if (is_admin()) {
    346                 $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term[0]->slug . "&post_type=" . get_post_type($pid);
    347             } else {
    348                 $link = get_term_link( $term[0] );
    349             }
    350             return '<a href="' . $link . '">' . $term[0]->name . '</a>';
    351         }
    352         if ($label == 'id' || $label == 'term_id') {
    353             return $term[0]->term_id;
    354         }
    355         return null;
    356     }
    357 
    358 
    359     //** for retrieving p2p connected posts
    360     // can output count, array of post objects, list of html links, list of text, a table of links, or select dropdown input values
    361     // note - modifies the URL output if inside wp-admin to stay in admin and to link to the edit post listing screen rather than the post permalink
    362     // $pid = can be single integer or array of post ID's
    363     // $p2pname = the name of the connection you made with "p2p_register_connection_type"
    364     // $dir = connection direction (to, from, any)
    365     // $output = how to format the return data
    366 
    367     function fetch_connected_items( $pid = 0, $p2pname, $dir = 'any', $output = 'html', $sep = ", " ) {
    368         if (!function_exists('p2p_register_connection_type')) return 'No P2P plugin!';      // make sure plugin is enabled
    369 
    370         // https://github.com/scribu/wp-posts-to-posts/wiki/Query-vars
    371 
    372         $args = array( 'connected_type' => $p2pname );
    373         switch( $dir ) {
    374             case "to" :
    375                 $args['connected_to'] = $pid;
    376             break;
    377             case "from" :
    378                 $args['connected_from'] = $pid;
    379             break;
    380             default :
    381                 $args['connected_items'] = $pid;    // this passes "any" for direction
    382             break;
    383         }
    384 
    385         if ($meta_key && $meta_value) {
    386             $args['connected_meta'] = array(
    387                 $meta_key => $meta_value
    388             );
    389         }
    390 
    391         $query = new WP_Query( $args );
    392         $items = array();
    393         $items = $query->posts;
    394 
    395         if ( is_wp_error( $items ) )
    396             return $items->get_error_message();;
    397 
    398         // output how many connected items
    399         if ($output == "count") {
    400             if ( empty( $items ) ) return 0;
    401             return count($items);
    402         }
    403 
    404 
    405         // simply output the array of matching post objects
    406         if ($output == "objects") {
    407             if ( empty( $items ) ) return false;
    408             return $items;
    409         }
    410 
    411         // create array with links
    412         if ($output == "html") {
    413             if ( empty( $items ) ) return false;
    414             foreach ( $items as $item ) {
    415                 // within admin, link to the admin edit post list screen, but filtering for this taxonomy term
    416                 if (is_admin()) {
    417                     $link = get_edit_post_link($item->ID);
    418                 } else {
    419                     $link = get_permalink($item->ID);
    420                 }
    421                 if ( is_wp_error( $link ) )
    422                     return $link->get_error_message();;
    423                 $item_list[] = '<a href="' . $link . '">' . $item->post_title . '</a>';
    424             }
    425             return $before . join( $sep, $item_list ) . $after;
    426 
    427             // return p2p_list_posts( p2p_type( $p2pname )->get_connected( $post->ID ) );  // a helpful function from scribu to do the same thing ;-)
    428         }
    429 
    430         // create plain string of comma-separated names
    431         if ($output == "text") {
    432             if ( empty( $items ) ) return false;
    433             foreach ($items as $item) {
    434                 $item_list[] = $item->post_title;
    435             }
    436             return $before . join( $sep, $item_list ) . $after;
    437         }
    438 
    439         // outputs a vertical stack table for items in the edit.php listing
    440         if ($output == "table") {
    441             if (!is_admin()) return false;
    442             if ( empty( $items ) ) return false;
    443             // generate table html
    444             $table = "<table>";
    445             $i = 1;
    446             $max = count($items);
    447             foreach ($items as $item) {
    448                 // avoid border on last entry
    449                 if ($i == $max) {
    450                     $table .= "<tr><td class=\"last\">";
    451                 } else {
    452                     $table .= "<tr><td>";
    453                 }
    454                 $table .= "<a href=\"".get_edit_post_link($item->ID)."\">$item->post_title</a></td></tr>";
    455                 $i++;
    456             }
    457             $table .= "</table>";
    458             return $table;
    459         }
    460 
    461         // outputs an array for populating select dropdown inputs
    462         if ($output == "select") {
    463             if ( empty( $items ) ) {
    464                 return array(array('name' => '[nothing to select]', 'value' => '0'));   // no user role matches, output empty list
    465             }
    466             foreach ($items as $item) {
    467                 $list[] = array('name' => $item->post_title, 'value' => intval($item->ID));
    468             }
    469             return $list;
    470         }
    471     }
    472 
    473 
    474     //** generates mini box html output of asset ------------------------------------------------------//
    475     function mini_asset_output($items) {
    476         if (!is_array($items)) {
    477             $items = array($items);
    478         }
    479         $html = "<div class=\"mini-asset-list\">\n";
    480         $html .= "<ul class=\"slider\">\n";
    481 
    482         foreach ($items as $item) {
    483 
    484             $view = get_permalink($item->ID);
    485             $edit = get_edit_post_link($item->ID);
    486             $title = get_the_title($item->ID);
    487             $services = somaFunctions::fetch_the_term_list($item->ID, 'service','','<br/>','','html');
    488             $status = somaFunctions::fetch_the_singular_term($item->ID, 'app_status');
    489             $type = $item->post_type;
    490             $img = somaFunctions::fetch_featured_image($item->ID);
    491 
    492 
    493             $html .= "<li class=\"item $status\">\n";
    494 
    495             $html .= "\t<ul>\n";
    496 
    497             // in admin, click image to edit the item, in front-end, click image to go to single asset view
    498             // if (is_admin()) {
    499             //  if ( $review ) {
    500             //      // add lightbox link to image
    501             //      $html .= "\t<li class=\"post-thumb\"><a class=\"lightbox\" rel=\"gallery\" href=\"{$img['large']['url']}\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
    502             //  } else {
    503             //      // add edit link to image
    504             //      $html .= "\t<li class=\"post-thumb\"><a href=\"". get_edit_post_link($item->ID) ."\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
    505             //  }
    506             // } else {
    507             //  // add view link to image
    508             //  $html .= "<li class=\"post-thumb\"><a href=\"". get_permalink($item->ID) . "\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
    509             // }
    510 
    511 
    512             $html .= "\t\t<li class=\"asset-thumb\">\n";
    513 
    514             $html .= "\t\t\t<a href=\"$edit\"><div class=\"fb-thumb\"><img src=\"{$img['thumbnail']['url']}\"/></a></div>\n";
    515             // $html .= "\t\t\t<a href=\"$edit\"><img src=\"{$img['thumbnail']['url']}\"/></a>\n";
    516             $html .= "\t\t</li>\n";
    517 
    518             $html .= "\t\t<li class=\"title\"><strong><a href=\"$edit\">". get_the_title($item->ID). "</a></strong></li>\n";
    519 
    520             // $html .= "\t\t<li class=\"services\">". $services . "</li>\n";
    521 
    522             // $html .= "\t\t<li class=\"creator\">". somaFunctions::fetch_post_author( $item->ID) ."</li>\n";
    523 
    524 
    525             if ($type == "classes") {
    526                 $conn = get_posts( array(
    527                     'post_type' => array('clients'),
    528                     'suppress_filters' => false,
    529                     'numberposts' => -1,
    530                     'connected' => $item->ID,
    531                 ) );
    532                 $max = somaFunctions::asset_meta('get', $item->ID, 'class_max');
    533                 if (!$max) $max = "?";
    534                 $count = count( $conn);
    535                 $html .= "\t\t<li class=\"count\">" .$count . " Enrolled / $max Max</li>\n";
    536                 $html .= "\t\t<li class=\"type\">$type</li>\n";     // minibox label
    537             } else {
    538                 $html .= "\t\t<li class=\"type\">$status</li>\n";   // minibox label
    539             }
    540 
    541             $html .= "\t</ul>\n";
    542             $html .= "</li>\n";
    543         }
    544         $html .= "</ul>\n";
    545         $html .= "</div>\n";
    546         return $html;
    547     }
    548 
    549     // need this?
    550     function extract_numbers($string) {
    551         preg_match_all('/([\d]+)/', $string, $match);
    552         return $match[0];
    553     }
    554 
    555     // automatically sends user to dashboard after login, instead of their profile page
    556     function dashboard_redirect($url) {
    557         global $user;
    558         // if ($user->roles[0] == 'foo')
    559         //  return 'wp-admin/admin.php?page=foo-user-page';
    560         return 'wp-admin/index.php';
    561     }
    562 
    563     /**
    564     * Performs a query for results within a set of multiple terms (matching any one of, not all)
    565     *
    566     * @param string $type (required) Post Type slug
    567     * @param string $tax (required) Taxonomy slug
    568     * @param array strings $terms (required) Term slugs to match within
    569     * @return array of objects
    570     */
    571     function multi_term_query($type = null, $tax = null, $terms = null) {
    572         global $wpdb;
    573 
    574         if (!$type || !$tax || !is_array($terms)) {
    575             wp_die('missing an argument...');
    576         }
    577 
    578         $querystr = "SELECT *
    579             FROM $wpdb->posts
    580             LEFT JOIN $wpdb->term_relationships ON($wpdb->posts.ID = $wpdb->term_relationships.object_id)
    581             LEFT JOIN $wpdb->term_taxonomy ON($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
    582             LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)
    583             WHERE $wpdb->posts.post_type = '$type'
    584             AND $wpdb->posts.post_status = 'publish'
    585             AND $wpdb->term_taxonomy.taxonomy = '$tax'
    586             AND (";
    587         $i = 1;
    588         foreach ($terms as $term) {
    589             $querystr .= "$wpdb->terms.slug = '$term'";
    590             if ($i < count($terms)) {
    591                     $querystr .= " OR ";
    592             }
    593             $i++;
    594         }
    595 
    596         $querystr .= ")\nORDER BY $wpdb->posts.post_date ASC";
    597         // echo $querystr;
    598         $posts = $wpdb->get_results($querystr, OBJECT);
    599         return $posts;
    600     }
    601 
    602 
    603     // add_action('post_submitbox_misc_actions','my_post_submitbox_misc_actions');
    604     function my_post_submitbox_misc_actions() {
    605         ?>
    606         <script type="text/javascript">
    607         jQuery(document).ready(function($) {
    608             $("#misc-publishing-actions select#post_status option[value='pending']").remove();
    609         });
    610         </script>
    611         <?php
    612     }
    613 
    614     // changes words throughout wordpress
    615     function modify_core_language( $translated ) {
    616          // $translated = str_replace( 'Publish', 'Ingest', $translated );
    617          // $translated = str_replace( 'Dashboard', 'Overview', $translated );
    618          $translated = str_replace( 'featured', 'assigned', $translated );
    619          $translated = str_replace( 'Start typing the title of a post', 'Start typing the title of an asset', $translated );    // p2p metabox
    620          // $translated = str_replace( 'Post', 'Article', $translated );
    621          return $translated;
    622     }
    623 
    624     // deletes all attachments (including the actual files on server) when a post is deleted -- custom post type args or this post's post_meta must be set for this to happen, so its not universal
    625     function delete_attachments_when_parents_die($post_id) {
    626         $metakill = somaFunctions::asset_meta('get', $post_id, 'delete_attachments_upon_deletion');
    627         $ptype = get_post_type($post_id);
    628         $ptobj = get_post_type_object($ptype);
    629         $typekill = $ptobj->delete_attachments_upon_deletion;
    630         if ( $metakill || $typekill ) {
    631             global $wpdb;
    632             $attids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = %s", $post_id, 'attachment')); // properly prepared safe query
    633             foreach ( $attids as $attid ) {
    634                 wp_delete_attachment($attid);
    635             }
    636         }
    637     }
    638 
    639     //** allows use of custom field data in the query vars URL string. without this, would need to call the query_posts() function and pass meta_key/meta_value...
    640     function custom_query_vars($qvars) {
    641         $qvars[] = "meta_key";
    642         $qvars[] = "meta_value";
    643         $qvars[] = "meta_compare";
    644         return $qvars;
    645     }
    646 
    647     //** modifies the QUERY before output ------------------------------------------------------//
    648     function filter_current_query($query) {
    649         return $query;
    650     }
    651 
    652     // this is much like filter_current_query
    653     function pre_get_posts($query) {
    654         // wp_die(var_dump($query));
    655         return $query;
    656     }
    657 
    658     /**
     5    function __construct() {
     6        add_action( 'init', array(__CLASS__,'init' ));
     7        add_action( 'admin_init', array(__CLASS__,'check_plugin_dependency' ));
     8        add_filter( 'query_vars', array(__CLASS__,'custom_query_vars' ));
     9        add_filter( 'parse_query', array(__CLASS__,'filter_current_query' ));           // empty at the moment
     10        add_filter( 'pre_get_posts', array(__CLASS__,'pre_get_posts'));
     11        add_action( 'before_delete_post', array(__CLASS__, 'delete_attachments_when_parents_die' ));        // have to do this "before", as wp_delete_post detaches all attachments before the 'delete_post' action, which left us unable to retrieve connected attachments...
     12        add_filter( 'gettext',  array(__CLASS__, 'modify_core_language'  ), 20, 3);
     13        add_filter( 'ngettext',  array(__CLASS__, 'modify_core_language'  ), 20, 3);
     14        // add_filter( 'login_redirect', array(__CLASS__, 'dashboard_redirect' ));
     15        add_filter( 'add_menu_classes', array(__CLASS__, 'show_pending_number'));
     16        // add_filter( 'wp_die_handler', array(__CLASS__, 'soma_wp_die_handler'),10,3);
     17        add_filter( 'editable_roles', array(__CLASS__, 'editable_roles'));
     18        add_filter( 'map_meta_cap', array(__CLASS__, 'admin_map_meta_cap'), 10, 4);
     19        remove_filter('check_comment_flood', 'check_comment_flood_db');                 // deal with "posting too quickly" problem....
     20        add_filter( 'edit_posts_per_page', array(__CLASS__, 'edit_list_length'));
     21        add_action( 'wp_ajax_unlink_file', array(__CLASS__, 'ajax_unlink_file'));
     22        add_action( 'wp_ajax_delete_attachment', array(__CLASS__, 'ajax_delete_attachment'));
     23        add_action( 'admin_notices', array(__CLASS__,'soma_admin_notices'), 100);
     24    }
     25
     26    function init() {
     27        // localization init needed?? no translations yet
     28        // load_plugin_textdomain( 'stock-media-asset-manager', SOMA_DIR . '/lang', basename( dirname( __FILE__ ) ) . '/lang' );
     29        // self::session_manager(); /// WARNING this occasionally causes PHP errors: "Cannot send session cache limiter - headers already sent" -- do we need this?
     30        self::wp_version_check();
     31        self::check_staff();
     32    }
     33
     34    // displays notices using the core wp messge box.
     35    function soma_admin_notices() {
     36        // if (soma_fetch_index($_GET['soma-notify']) == true) {
     37        // if (get_query_var('soma_notify') == true) {
     38        $notices = get_transient('soma_notices');                     // check if any waiting
     39            if ($notices === false) return;
     40            foreach ($notices as $notice) {
     41                if (empty($notice['msg'])) continue;
     42                if (empty($notice['type'])) $notice['type'] == 'updated';
     43                echo "<div class='{$notice['type']}'>";
     44                echo "<p>{$notice['msg']}</p>";
     45                echo "</div>";
     46            }
     47        // }
     48        delete_transient('soma_notices');
     49    }
     50
     51    // adds an admin notice message to the queue. can use 'updated', or 'error'
     52    function queue_notice($type = 'updated', $msg) {
     53        $notices = get_transient( 'soma_notices' );                         // grab any existing notices
     54        if ($notices === false || !is_array($notices)) $notices = array();  // or init
     55        $notices[] = array('type' => $type, 'msg' => $msg);                 // add to the queue
     56        set_transient( 'soma_notices', $notices );                          // store it
     57    }
     58
     59    // adds an admin notice as well as reloads the page to display the notice. good for returning the result of a custom manipulation function without leaving the page
     60    function completion_notice($type, $msg) {
     61        somaFunctions::queue_notice($type, $msg);
     62        // $result = add_query_arg( 'soma_notify', 'true', $_SERVER['HTTP_REFERER'] );      // used to be needed to trigger, but now we just check for the transient itself
     63        // wp_redirect( $result );
     64        wp_redirect( $_SERVER['HTTP_REFERER'] );
     65        exit;   // this halts everything, so this better be your last function call
     66    }
     67
     68    // set if user has staff privileges (default to editors)
     69    function check_staff() {
     70        $privs = 'edit_others_posts';
     71        $privs = apply_filters('soma_staff_privs', $privs);
     72        define( 'SOMA_STAFF', current_user_can( $privs ));
     73    }
     74
     75    // changes how many items are shown per page in /wp-admin/edit.php
     76    function edit_list_length() {
     77        return 40;
     78    }
     79
     80    // checks if something is truly empty (not set) or null, and not simply set to a valid but negative value, like false or - 0 (0 as an integer) - 0.0 (0 as a float) - "0" (0 as a string)
     81    // NOTE: THIS DOESN'T AVOID THE PHP NOTICE ERROR IF SOMETHING DOESN'T EXIST (not set)
     82    function is_blank( $value ) {
     83        return empty( $value ) && !is_numeric( $value ) && $value !== false;
     84    }
     85
     86    // checks if array is associative or not
     87    function array_is_associative($arr) {
     88        if (!is_array($arr)) return false;
     89        return array_keys($arr) !== range(0, count($arr) - 1);
     90    }
     91
     92    // returns URL to facebook image from facebook ID stored in post_meta
     93    function fetch_facebook_pic($pid, $size = "square") {
     94        // sizes: square, small, normal, large
     95
     96        $fid = somaFunctions::asset_meta('get', $pid, 'facebook_id');
     97        if (!$fid) return SOMA_IMG . '/generic-female-thumb.png';
     98
     99        // $userurl = "http://graph.facebook.com/$fid";
     100        // $user = json_decode(file_get_contents($userurl));
     101        // $feedurl = "http://graph.facebook.com/$fid/feed";
     102        // $feed = json_decode(file_get_contents($feedurl));
     103        return "https://graph.facebook.com/$fid/picture?type=$size";
     104    }
     105
     106    // since our postmeta is serialized, to query posts by postmeta requires retrieving a set of posts and then checking each entry against the desired postmeta for matches
     107    function fetch_posts($args, $meta_key = null, $meta_value = null) {
     108        if (!is_array($args)) return null;
     109        $args['numberposts'] = -1;  // always retrieve all, not just first 5
     110        $items = get_posts($args);
     111        $matches = array();
     112        foreach ($items as $item) {
     113            $val = somaFunctions::asset_meta('get', $item->ID, $meta_key);
     114            if ($val == $meta_value) {
     115                $matches[] = $item;
     116            }
     117        }
     118        return $matches;
     119    }
     120
     121    // retrieves array of user objects for a given role name
     122    function get_role_members_OLD($rolename) {
     123        $blogusers = get_users_of_blog();
     124        $members = array();
     125        foreach ($blogusers as $user) {
     126            $userdata = get_userdata($user->ID);
     127            // check role name
     128            if ($userdata->wp_capabilities[$rolename] == '1') {
     129                // add to new role array
     130                $members[] = $userdata;
     131            }
     132        }
     133        return $members;
     134    }
     135
     136
     137    // retrieves array of user objects for a given role name
     138    function get_role_members($roles = null) {
     139        if (!$roles) return false;
     140        // convert to array if single
     141        if (!is_array($roles)) {
     142            $roles = array($roles);
     143        }
     144        // init output array
     145        $members = array();
     146        global $wp_roles;
     147        foreach ($roles as $role) {
     148            if (!array_key_exists($role, $wp_roles->roles)) continue;   // abort if given a non-existant role (otherwise the user_query will return everyone)
     149            $wp_user_search = new WP_User_Query( array( 'role' => $role) );
     150            // wp_die(var_dump($wp_user_search));
     151            $users = $wp_user_search->get_results();
     152            if (empty($users)) continue;
     153            foreach ($users as $user) {
     154                $members[] = $user;
     155            }
     156        }
     157        return $members;
     158    }
     159
     160    // retrieves name of role assigned to pass user id or current user if not provided
     161    function get_user_role( $uid = null ) {
     162        if (!$uid) {
     163            $user = wp_get_current_user();
     164        } else {
     165            $user = new WP_User( $uid );
     166        }
     167        if ( !empty( $user->roles ) && is_array( $user->roles ) ) {
     168            foreach ( $user->roles as $role )
     169                return $role;
     170        }
     171    }
     172
     173    // comparison function for usort - allows sorting of arrays of objects by object properties
     174    function usort_displaynames($a, $b) {
     175        if ($a->display_name == $b->display_name)
     176            return 0;
     177        else
     178            return ($a->display_name < $b->display_name ? -1 : 1);
     179    }
     180
     181    // comparison function for usort - allows sorting of arrays of objects by object properties
     182    function usort_ids($a, $b) {
     183        if ($a->ID == $b->ID)
     184            return 0;
     185        else
     186            return ($a->ID < $b->ID ? -1 : 1);
     187    }
     188
     189    // checks to see if $_GET or $_POST values are set, avoids Undefined index error
     190    function fetch_index($array, $index) {
     191        if (!is_array($array)) return null;
     192        return isset($array[$index]) ? $array[$index] : null;
     193    }
     194
     195    //
     196    function fetch_author($author_id = null, $output = "display_name") {
     197        if ($author_id != null) {
     198            $user = get_userdata($author_id);
     199            if ($user == null) {
     200                return new WP_Error('missing','can\'t find the user...');
     201            }
     202        } else {
     203            return new WP_Error('missing','must provide author id...');
     204        }
     205        // $code = get_the_author_meta( somaMetaboxes::$meta_prefix .'user_code', $post->post_author );
     206        // $code = get_the_author_meta( 'user_exclusive', $post->post_author );
     207
     208        if ($output == "link") {
     209            if (is_admin()) {
     210                $url = admin_url() . "edit.php?author=" . $user->ID;
     211                $url .=  $type ? "&post_type=".$type : "";
     212            } else {
     213                $url = home_url() . "?author=" . $user->ID;
     214                $url .=  $type ? "&post_type=" . $type : "";
     215            }
     216            return "<a href=\"" . $url . "\">". $user->display_name . "</a>";
     217        }
     218        return $user->$output;
     219    }
     220
     221    //
     222    function fetch_post_author($post_id = null, $output = "display_name" ) {
     223        if (!$post_id) {
     224            global $post;
     225            if ($post == null) {
     226                return new WP_Error('missing','can\'t find the post...');
     227            }
     228        } else {
     229            $post = get_post($post_id);
     230        }
     231        $type = $post->post_type;
     232        $user = get_userdata($post->post_author);
     233
     234        // $code = get_the_author_meta( somaMetaboxes::$meta_prefix .'user_code', $post->post_author );
     235        // $code = get_the_author_meta( 'user_exclusive', $post->post_author );
     236
     237        if ($output == "link") {
     238            if (is_admin()) {
     239                $url = admin_url() . "edit.php?author=" . $user->ID;
     240                $url .=  $type ? "&post_type=".$type : "";
     241            } else {
     242                $url = home_url() . "?author=" . $user->ID;
     243                $url .=  $type ? "&post_type=" . $type : "";
     244            }
     245            return "<a href=\"" . $url . "\">". $user->display_name . "</a>";
     246        }
     247        return $user->$output;
     248    }
     249
     250    //
     251    function fetch_current_user($output = "display_name") {
     252        global $current_user;
     253        if ($current_user == null) {
     254            return new WP_Error('missing','can\'t find the user...');
     255        }
     256
     257        if ($output == "link") {
     258            if (is_admin()) {
     259                $url = admin_url() . "edit.php?author=" . $current_user->ID;
     260                $url .=  $type ? "&post_type=".$type : "";
     261            } else {
     262                $url = home_url() . "?author=" . $current_user->ID;
     263                $url .=  $type ? "&post_type=" . $type : "";
     264            }
     265            return "<a href=\"" . $url . "\">". $current_user->display_name . "</a>";
     266        }
     267        return $current_user->$output;
     268    }
     269
     270
     271    //** clone of core get_the_term_list() - modifies the URL output if inside admin to stay in admin and to link to the edit post listing screen, also - can output comma separated string instead of links
     272    function fetch_the_term_list( $id = null, $taxonomy, $before = '', $sep = ', ', $after = '', $output = 'html' ) {
     273        if (is_null($id)) return false;
     274        $terms = wp_get_object_terms( $id, $taxonomy );
     275        $type = get_post_type($id);
     276        if ( is_wp_error( $terms ) )
     277            return $terms->get_error_message();
     278        if ( empty( $terms ) )
     279            return false;
     280        // create array with links
     281        if ($output == "html") {
     282            foreach ( $terms as $term ) {
     283                // within admin, link to the admin edit post list screen, but filtering for this taxonomy term
     284                if (is_admin()) {
     285                    $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term->slug . "&post_type=" . $type;
     286                } else {
     287                    $link = get_term_link( $term, $taxonomy );
     288                }
     289                if ( is_wp_error( $link ) )
     290                    return $link->get_error_message();
     291                $term_links[] = '<a href="' . $link . '" rel="tag">' . $term->name . '</a>';
     292            }
     293        // create plain string of comma-separated names
     294        }
     295        if ($output == "text") {
     296            foreach ($terms as $term) {
     297                $term_links[] = $term->name;
     298            }
     299        }
     300
     301        // outputs a vertical stack table for items in the edit.php listing
     302        if ($output == "table") {
     303            if (!is_admin()) return false;
     304
     305            $table = "<table>";
     306            $i = 1;
     307            $max = count($terms);
     308            foreach ($terms as $term) {
     309                // avoid border on last entry
     310                if ($i == $max) {
     311                    $table .= "<tr><td class=\"last\">";
     312                } else {
     313                    $table .= "<tr><td>";
     314                }
     315                $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term->slug . "&post_type=" . $type;
     316                $table .= "<a href=\"".$link."\" rel=\"tag\">$term->name</a></td></tr>";
     317                $i++;
     318            }
     319            $table .= "</table>";
     320            return $table;
     321        }
     322
     323        return $before . join( $sep, $term_links ) . $after;
     324    }
     325
     326    // retrieves taxonomy terms that are used in a singular way (only one possible state, ie ON or OFF)
     327    function fetch_the_singular_term( $post, $taxonomy, $label = "slug" ) {
     328        if (is_wp_error($post)) return $post;
     329        if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
     330        if (is_object($post)) {
     331            $pid = $post->ID;
     332        } else {
     333            $pid = intval($post);
     334        }
     335
     336        $term = wp_get_object_terms( $pid, $taxonomy );
     337        if (is_wp_error($term) || empty($term)) return null;            // taxonomy doesn't exist OR there are no terms assigned
     338        if ($label == 'slug') {
     339            return $term[0]->slug;
     340        }
     341        if ($label == 'name') {
     342            return $term[0]->name;
     343        }
     344        if ($label == 'link') {
     345            if (is_admin()) {
     346                $link = admin_url() . "edit.php?" . $taxonomy . "=" . $term[0]->slug . "&post_type=" . get_post_type($pid);
     347            } else {
     348                $link = get_term_link( $term[0] );
     349            }
     350            return '<a href="' . $link . '">' . $term[0]->name . '</a>';
     351        }
     352        if ($label == 'id' || $label == 'term_id') {
     353            return $term[0]->term_id;
     354        }
     355        return null;
     356    }
     357
     358
     359    //** for retrieving p2p connected posts
     360    // can output count, array of post objects, list of html links, list of text, a table of links, or select dropdown input values
     361    // note - modifies the URL output if inside wp-admin to stay in admin and to link to the edit post listing screen rather than the post permalink
     362    // $pid = can be single integer or array of post ID's
     363    // $p2pname = the name of the connection you made with "p2p_register_connection_type"
     364    // $dir = connection direction (to, from, any)
     365    // $output = how to format the return data
     366
     367    function fetch_connected_items( $pid = 0, $p2pname, $dir = 'any', $output = 'html', $sep = ", " ) {
     368        if (!function_exists('p2p_register_connection_type')) return 'No P2P plugin!';      // make sure plugin is enabled
     369
     370        // https://github.com/scribu/wp-posts-to-posts/wiki/Query-vars
     371
     372        $args = array( 'connected_type' => $p2pname );
     373        switch( $dir ) {
     374            case "to" :
     375                $args['connected_to'] = $pid;
     376            break;
     377            case "from" :
     378                $args['connected_from'] = $pid;
     379            break;
     380            default :
     381                $args['connected_items'] = $pid;    // this passes "any" for direction
     382            break;
     383        }
     384
     385        if ($meta_key && $meta_value) {
     386            $args['connected_meta'] = array(
     387                $meta_key => $meta_value
     388            );
     389        }
     390
     391        $query = new WP_Query( $args );
     392        $items = array();
     393        $items = $query->posts;
     394
     395        if ( is_wp_error( $items ) )
     396            return $items->get_error_message();;
     397
     398        // output how many connected items
     399        if ($output == "count") {
     400            if ( empty( $items ) ) return 0;
     401            return count($items);
     402        }
     403
     404
     405        // simply output the array of matching post objects
     406        if ($output == "objects") {
     407            if ( empty( $items ) ) return false;
     408            return $items;
     409        }
     410
     411        // create array with links
     412        if ($output == "html") {
     413            if ( empty( $items ) ) return false;
     414            foreach ( $items as $item ) {
     415                // within admin, link to the admin edit post list screen, but filtering for this taxonomy term
     416                if (is_admin()) {
     417                    $link = get_edit_post_link($item->ID);
     418                } else {
     419                    $link = get_permalink($item->ID);
     420                }
     421                if ( is_wp_error( $link ) )
     422                    return $link->get_error_message();;
     423                $item_list[] = '<a href="' . $link . '">' . $item->post_title . '</a>';
     424            }
     425            return $before . join( $sep, $item_list ) . $after;
     426
     427            // return p2p_list_posts( p2p_type( $p2pname )->get_connected( $post->ID ) );  // a helpful function from scribu to do the same thing ;-)
     428        }
     429
     430        // create plain string of comma-separated names
     431        if ($output == "text") {
     432            if ( empty( $items ) ) return false;
     433            foreach ($items as $item) {
     434                $item_list[] = $item->post_title;
     435            }
     436            return $before . join( $sep, $item_list ) . $after;
     437        }
     438
     439        // outputs a vertical stack table for items in the edit.php listing
     440        if ($output == "table") {
     441            if (!is_admin()) return false;
     442            if ( empty( $items ) ) return false;
     443            // generate table html
     444            $table = "<table>";
     445            $i = 1;
     446            $max = count($items);
     447            foreach ($items as $item) {
     448                // avoid border on last entry
     449                if ($i == $max) {
     450                    $table .= "<tr><td class=\"last\">";
     451                } else {
     452                    $table .= "<tr><td>";
     453                }
     454                $table .= "<a href=\"".get_edit_post_link($item->ID)."\">$item->post_title</a></td></tr>";
     455                $i++;
     456            }
     457            $table .= "</table>";
     458            return $table;
     459        }
     460
     461        // outputs an array for populating select dropdown inputs
     462        if ($output == "select") {
     463            if ( empty( $items ) ) {
     464                return array(array('name' => '[nothing to select]', 'value' => '0'));   // no user role matches, output empty list
     465            }
     466            foreach ($items as $item) {
     467                $list[] = array('name' => $item->post_title, 'value' => intval($item->ID));
     468            }
     469            return $list;
     470        }
     471    }
     472
     473
     474    //** generates mini box html output of asset ------------------------------------------------------//
     475    function mini_asset_output($items) {
     476        if (!is_array($items)) {
     477            $items = array($items);
     478        }
     479        $html = "<div class=\"mini-asset-list\">\n";
     480        $html .= "<ul class=\"slider\">\n";
     481
     482        foreach ($items as $item) {
     483
     484            $view = get_permalink($item->ID);
     485            $edit = get_edit_post_link($item->ID);
     486            $title = get_the_title($item->ID);
     487            $services = somaFunctions::fetch_the_term_list($item->ID, 'service','','<br/>','','html');
     488            $status = somaFunctions::fetch_the_singular_term($item->ID, 'app_status');
     489            $type = $item->post_type;
     490            $img = somaFunctions::fetch_featured_image($item->ID);
     491
     492
     493            $html .= "<li class=\"item $status\">\n";
     494
     495            $html .= "\t<ul>\n";
     496
     497            // in admin, click image to edit the item, in front-end, click image to go to single asset view
     498            // if (is_admin()) {
     499            //  if ( $review ) {
     500            //      // add lightbox link to image
     501            //      $html .= "\t<li class=\"post-thumb\"><a class=\"lightbox\" rel=\"gallery\" href=\"{$img['large']['url']}\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
     502            //  } else {
     503            //      // add edit link to image
     504            //      $html .= "\t<li class=\"post-thumb\"><a href=\"". get_edit_post_link($item->ID) ."\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
     505            //  }
     506            // } else {
     507            //  // add view link to image
     508            //  $html .= "<li class=\"post-thumb\"><a href=\"". get_permalink($item->ID) . "\"><img src=\"{$img['thumbnail']['url']}\" /></a></li>\n";
     509            // }
     510
     511
     512            $html .= "\t\t<li class=\"asset-thumb\">\n";
     513
     514            $html .= "\t\t\t<a href=\"$edit\"><div class=\"fb-thumb\"><img src=\"{$img['thumbnail']['url']}\"/></a></div>\n";
     515            // $html .= "\t\t\t<a href=\"$edit\"><img src=\"{$img['thumbnail']['url']}\"/></a>\n";
     516            $html .= "\t\t</li>\n";
     517
     518            $html .= "\t\t<li class=\"title\"><strong><a href=\"$edit\">". get_the_title($item->ID). "</a></strong></li>\n";
     519
     520            // $html .= "\t\t<li class=\"services\">". $services . "</li>\n";
     521
     522            // $html .= "\t\t<li class=\"creator\">". somaFunctions::fetch_post_author( $item->ID) ."</li>\n";
     523
     524
     525            if ($type == "classes") {
     526                $conn = get_posts( array(
     527                    'post_type' => array('clients'),
     528                    'suppress_filters' => false,
     529                    'numberposts' => -1,
     530                    'connected' => $item->ID,
     531                ) );
     532                $max = somaFunctions::asset_meta('get', $item->ID, 'class_max');
     533                if (!$max) $max = "?";
     534                $count = count( $conn);
     535                $html .= "\t\t<li class=\"count\">" .$count . " Enrolled / $max Max</li>\n";
     536                $html .= "\t\t<li class=\"type\">$type</li>\n";     // minibox label
     537            } else {
     538                $html .= "\t\t<li class=\"type\">$status</li>\n";   // minibox label
     539            }
     540
     541            $html .= "\t</ul>\n";
     542            $html .= "</li>\n";
     543        }
     544        $html .= "</ul>\n";
     545        $html .= "</div>\n";
     546        return $html;
     547    }
     548
     549    // need this?
     550    function extract_numbers($string) {
     551        preg_match_all('/([\d]+)/', $string, $match);
     552        return $match[0];
     553    }
     554
     555    // automatically sends user to dashboard after login, instead of their profile page
     556    function dashboard_redirect($url) {
     557        global $user;
     558        // if ($user->roles[0] == 'foo')
     559        //  return 'wp-admin/admin.php?page=foo-user-page';
     560        return 'wp-admin/index.php';
     561    }
     562
     563    /**
     564    * Performs a query for results within a set of multiple terms (matching any one of, not all)
     565    *
     566    * @param string $type (required) Post Type slug
     567    * @param string $tax (required) Taxonomy slug
     568    * @param array strings $terms (required) Term slugs to match within
     569    * @return array of objects
     570    */
     571    function multi_term_query($type = null, $tax = null, $terms = null) {
     572        global $wpdb;
     573
     574        if (!$type || !$tax || !is_array($terms)) {
     575            wp_die('missing an argument...');
     576        }
     577
     578        $querystr = "SELECT *
     579            FROM $wpdb->posts
     580            LEFT JOIN $wpdb->term_relationships ON($wpdb->posts.ID = $wpdb->term_relationships.object_id)
     581            LEFT JOIN $wpdb->term_taxonomy ON($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
     582            LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)
     583            WHERE $wpdb->posts.post_type = '$type'
     584            AND $wpdb->posts.post_status = 'publish'
     585            AND $wpdb->term_taxonomy.taxonomy = '$tax'
     586            AND (";
     587        $i = 1;
     588        foreach ($terms as $term) {
     589            $querystr .= "$wpdb->terms.slug = '$term'";
     590            if ($i < count($terms)) {
     591                    $querystr .= " OR ";
     592            }
     593            $i++;
     594        }
     595
     596        $querystr .= ")\nORDER BY $wpdb->posts.post_date ASC";
     597        // echo $querystr;
     598        $posts = $wpdb->get_results($querystr, OBJECT);
     599        return $posts;
     600    }
     601
     602
     603    // add_action('post_submitbox_misc_actions','my_post_submitbox_misc_actions');
     604    function my_post_submitbox_misc_actions() {
     605        ?>
     606        <script type="text/javascript">
     607        jQuery(document).ready(function($) {
     608            $("#misc-publishing-actions select#post_status option[value='pending']").remove();
     609        });
     610        </script>
     611        <?php
     612    }
     613
     614    // changes words throughout wordpress
     615    function modify_core_language( $translated ) {
     616         // $translated = str_replace( 'Publish', 'Ingest', $translated );
     617         // $translated = str_replace( 'Dashboard', 'Overview', $translated );
     618         $translated = str_replace( 'featured', 'assigned', $translated );
     619         $translated = str_replace( 'Start typing the title of a post', 'Start typing the title of an asset', $translated );    // p2p metabox
     620         // $translated = str_replace( 'Post', 'Article', $translated );
     621         return $translated;
     622    }
     623
     624    // deletes all attachments (including the actual files on server) when a post is deleted -- custom post type args or this post's post_meta must be set for this to happen, so its not universal
     625    function delete_attachments_when_parents_die($post_id) {
     626        $metakill = somaFunctions::asset_meta('get', $post_id, 'delete_attachments_upon_deletion');
     627        $ptype = get_post_type($post_id);
     628        $ptobj = get_post_type_object($ptype);
     629        $typekill = $ptobj->delete_attachments_upon_deletion;
     630        if ( $metakill || $typekill ) {
     631            global $wpdb;
     632            $attids = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = %s", $post_id, 'attachment')); // properly prepared safe query
     633            foreach ( $attids as $attid ) {
     634                wp_delete_attachment($attid);
     635            }
     636        }
     637    }
     638
     639    //** allows use of custom field data in the query vars URL string. without this, would need to call the query_posts() function and pass meta_key/meta_value...
     640    function custom_query_vars($qvars) {
     641        $qvars[] = "meta_key";
     642        $qvars[] = "meta_value";
     643        $qvars[] = "meta_compare";
     644        return $qvars;
     645    }
     646
     647    //** modifies the QUERY before output ------------------------------------------------------//
     648    function filter_current_query($query) {
     649        return $query;
     650    }
     651
     652    // this is much like filter_current_query
     653    function pre_get_posts($query) {
     654        // wp_die(var_dump($query));
     655        return $query;
     656    }
     657
     658    /**
    659659     * Merge new query parameters with existing parameters.
    660660     *
     
    678678    }
    679679
    680     //** retrieves featured image of a post, or an attachment itself and returns array of intermediate sizes, paths, urls ----------------------------------------------------------------------------------//
    681     public function fetch_featured_image($post = null, $specific = null) {
    682         if (is_wp_error($post)) return $post;
    683         if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
    684         if (is_object($post)) {
    685             $pid = $post->ID;
    686         } else {
    687             $pid = intval($post);
    688         }
    689 
    690         $img = array(); // container
    691 
    692         // fetch id of featured image attachment
    693         if (has_post_thumbnail($pid)) {
    694             $att_id = get_post_thumbnail_id($pid);
    695         }
    696 
    697         // post is already attachment - just pass ID
    698         if (get_post_type($pid) == 'attachment') {
    699             $att_id = $pid;
    700         }
    701 
    702         // some attachment successfully found
    703         if ($att_id) {
    704             $att_meta = wp_get_attachment_metadata($att_id);        // get metadata of attachment
    705             if ( !empty( $att_meta['subdir'] ) ) {                  // the original SMAM system generates this custom attachment meta when it created upload directories based on type/author. If it exists, use it
    706                 $subdir = $att_meta['subdir'] . '/';
    707             }
    708             $dirname = dirname( $att_meta['file'] );                // when "Organize my uploads into month- and year-based folders" is turned on, the date subfolder is stored in file path
    709             if ( $dirname == "." ) {    // no subdirectory path is needed
    710                 $subdir = "/";
    711             } else {
    712                 $subdir = '/'. dirname($att_meta['file']) . '/';
    713             }
    714             // build the paths from the base media upload dir and the subdir
    715             $media_path = WP_MEDIA_DIR . $subdir;
    716             $media_url = WP_MEDIA_URL . $subdir;
    717 
    718             // NEW ARRAY
    719             $img['id'] = $att_id;
    720 
    721             // 'sizes' key will only exist if the uploaded image was equal or larger than the site option for thumbnail size
    722             if ( !is_null( somaFunctions::fetch_index( $att_meta, 'sizes' ) ) && !empty( $att_meta[ 'sizes' ] ) ) {
    723                 // loop through all available image sizes, including any custom ones via add_image_size()
    724                 foreach ($att_meta['sizes'] as $size => $data) {
    725                     $img[$size]['file']     =   $data['file'];
    726                     $img[$size]['height']   =   $data['height'];
    727                     $img[$size]['width']    =   $data['width'];
    728                     $img[$size]['url']      =   $media_url . $img[$size]['file'];
    729                     $img[$size]['path']     =   $media_path . $img[$size]['file'];
    730                 }
    731                 $img['full']['file']    =   basename($att_meta['file']);    // $att_meta['file'] contains the entire server path, so extract just the name
    732                 $img['full']['url']     =   $media_url . $img['full']['file'];
    733                 $img['full']['path']    =   $media_path . $img['full']['file'];
    734                 $img['full']['height']  =   $att_meta['height'];
    735                 $img['full']['width']   =   $att_meta['width'];
    736                 $img['loc']['path']     =   $media_path;
    737                 $img['loc']['url']      =   $media_url;
    738 
    739             // populate thumb data with the direct file info, as the uploaded image was smaller or equal to the site option for thumbnail size. Yes, the thumb and the full img info are the same in this case...
    740             } else {
    741                 $img['thumbnail']['file']   =   basename($att_meta['file']);
    742                 $img['thumbnail']['height'] =   $att_meta['height'];
    743                 $img['thumbnail']['width']  =   $att_meta['width'];
    744                 $img['thumbnail']['url']    =   WP_MEDIA_URL . '/'. $att_meta['file'];  // don't include subdir when building paths, as we're taking path directly from 'file', which already includes it...
    745                 $img['thumbnail']['path']   =   WP_MEDIA_DIR . '/'. $att_meta['file'];
    746                 $img['full']['file']    =   basename($att_meta['file']);
    747                 $img['full']['url']     =   WP_MEDIA_URL . '/'. $att_meta['file'];
    748                 $img['full']['path']    =   WP_MEDIA_DIR . '/'. $att_meta['file'];
    749                 $img['full']['height']  =   $att_meta['height'];
    750                 $img['full']['width']   =   $att_meta['width'];
    751                 $img['loc']['path']     =   WP_MEDIA_DIR;
    752                 $img['loc']['url']      =   WP_MEDIA_URL;
    753 
    754                 //** FUTURE NOTE: might be good when there isn't a medium or large version of the image to default to some kind of "missing" image that actually says "image was too small"
    755             }
    756 
    757             $img['mime']            =   get_post_mime_type($att_id);                                            // mime-type of file
    758             $img['date']            =   get_the_date('M j, Y',$att_id) ." - ". get_the_time('h:iA',$att_id);    // date attachment was created
    759             $img['orientation']     =   somaFunctions::fetch_index($att_meta, 'orientation');                   // custom attribute, generated with ACME system
    760             $img['original']        =   somaFunctions::fetch_index($att_meta, 'original');                      // custom attribute, generated with ACME system
    761 
    762             // should we pass along? too confusing... use the typical meta fields below instead
    763             // $img['image_meta'] = $att_meta['image_meta'];
    764 
    765             // need to fetch the post from the table to get all the extra fields
    766             $att_post = get_post($att_id);
    767             $img['title'] = $att_post->post_title;
    768             $img['description'] = $att_post->post_content;
    769             $img['caption'] = $att_post->post_excerpt;
    770             $img['alt'] = get_post_meta($att_id, '_wp_attachment_image_alt', true);
    771 
    772         } else {
    773         // nothing found, return generic placeholder image
    774             $img['id'] = false;
    775             $sizes = get_intermediate_image_sizes();
    776             foreach ($sizes as $size) {
    777                 $img[$size]['url'] = SOMA_IMG . 'placeholder-image.png';
    778             }
    779             $img['thumbnail']['width'] = SOMA_THUMB_WIDTH;
    780             $img['thumbnail']['height'] = SOMA_THUMB_HEIGHT;
    781             $img['full']['name'] = 'MISSING IMAGE';
    782             $img['full']['url'] = SOMA_IMG . 'placeholder-image.png';
    783             $img['file']['path'] = SOMA_DIR . 'images/placeholder-image.png';
    784             $img['file']['file'] = 'placeholder-image.png';
    785         }
    786 
    787         // return just the requested URL
    788         if (!is_null($specific)) {
    789             if ($specific == 'filename') return $img['full']['file'];
    790             if (isset($att_meta['sizes']) && is_array($att_meta['sizes'])) {
    791                 if (array_key_exists($specific, $img)) {
    792                     return $img[$specific]['url'];          // this allows for custom image sizes to be passed via $specific
    793                 } else {
    794                     return $img['full']['url'];         // couldn't find the size requested, so just give original file
    795                 }
    796             } else {
    797                 return null;                                                            // no sizes have been generated for this attachment
    798             }
    799         } else {
    800             return $img;    // return array of variants
    801         }
    802 
    803     }
    804 
    805     // handles saving and retrieving post_meta via serialized arrays
    806     public function asset_meta( $action = null, $post = null, $key = null, $value = null, $serialize = null, $use_prefix = true ) {
    807         if (is_wp_error($post)) return $post;
    808         if ( empty($post) || empty($action) ) return new WP_Error('missing', "Must pass ID and action...");
    809         if (is_object($post)) {
    810             $pid = $post->ID;
    811         } else {
    812             $pid = intval($post);
    813         }
    814 
    815 
    816         global $soma_options;                                       // fetch options
    817         if ( $serialize === null ) {
    818             $serialize = somaFunctions::fetch_index($soma_options, 'meta_serialize');           // use default var if not passed in params
    819             if ($serialize == 1) {                                  // explicit true if evaluates as true
    820                 $serialize = true;
    821             } else {
    822                 $serialize = false;                                 // default false
    823             }
    824         }
    825         $prefix = $soma_options['meta_prefix'];
    826         // if we're supposed to use a prefix and we have one...
    827         if ( $use_prefix && !empty($prefix) ) {
    828             if ( $serialize ) {
    829                 $meta_key =  $prefix . "_asset_meta";
    830             } else {
    831                 $meta_key = $prefix . "_" . $key;
    832             }
    833         } else {
    834             // just use the given key, no prefix
    835             $meta_key = $key;
    836         }
    837 
    838 
    839 
    840         switch ( $action ) {
    841             case ('save') :
    842                 if (!$key) return new WP_Error('missing', "Must specify a field to save to...");
    843                 if (!$value) return new WP_Error('missing', "Missing a value for $key...");
    844 
    845                 $post_meta = get_post_meta( $pid, $meta_key, true);         // retrieve meta
    846                 if (empty($value)) {
    847                     if ( $serialize ) {
    848                         unset($post_meta[$key]);                            // remove key if value is empty (otherwise key will be saved with blank value)
    849                     } else {
    850                         return delete_post_meta( $pid, $meta_key );         // trash meta because value is empty
    851                     }
    852                 } else {                                                    // set new value
    853                     if ( $serialize ) {
    854                         $post_meta[$key] = $value;
    855                     } else {
    856                         $post_meta = $value;
    857                     }
    858                 }
    859                 return update_post_meta( $pid, $meta_key, $post_meta );     // rewrite the array to post_meta
    860             break;
    861 
    862             case ('get') :
    863                 $post_meta = get_post_meta( $pid, $meta_key, true);         // retrieve meta
    864                 if ( $serialize ) {
    865                     if (!$key) return $post_meta;                           // return whole meta array if no field specified
    866                     return $post_meta[$key];                                // return field
    867                 } else {
    868                     return $post_meta;
    869                 }
    870             break;
    871 
    872             case ('delete') :
    873                 // if (!$key) return delete_post_meta( $pid, $meta_key );   // note: this deletes the whole meta_key, all fields lost
    874                 if (!$key) return new WP_Error('missing', "Missing a key...");
    875                 $post_meta = get_post_meta( $pid, $meta_key, true);                 // retrieve meta
    876                 if ( !empty( $post_meta ) ) {                                       // if there was data to be deleted, proceed
    877                     if ( $serialize ) {
    878                         unset($post_meta[$key]);                                    // trash meta
    879                         return update_post_meta( $pid, $meta_key, $post_meta );     // rewrite the array to post_meta
    880                     } else {
    881                         return delete_post_meta( $pid, $meta_key );                 // trash meta
    882                     }
    883                 } else {                                                // nothing to delete, so don't bother (this happens when post gets saved with fields still blank, and the save_post functions attempt to clear the metadata)
    884                     return false;
    885                 }
    886             break;
    887 
    888             default :
    889                 return new WP_Error('action', "First parameter must be 'save', 'get', or 'delete'...");
    890             break;
    891         }
    892     }
    893 
    894 
    895     // returns all attachments (except featured image)
    896     public function fetch_attached_media($post = null, $mime = null, $include_featured = false) {
    897         if (is_wp_error($post)) return $post;
    898         if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
    899         if (is_object($post)) {
    900             $pid = $post->ID;
    901         } else {
    902             $pid = intval($post);
    903         }
    904 
    905         $args = array(
    906             'post_parent' => $pid,
    907             'post_type' => 'attachment',
    908             'numberposts' => -1,
    909             'post_status' => 'any',
    910             'orderby' => 'menu_order',
    911             'order' => 'ASC',
    912             'exclude' => get_post_thumbnail_id($pid),
    913         );
    914         if ($include_featured == true) {
    915             unset($args['exclude']);
    916         }
    917         if (!empty($mime)) {
    918             // only return requested media type (audio/mpeg, video/mp4, image/jpeg, application/pdf, application/zip)
    919             $args['post_mime_type'] = $mime;
    920         }
    921         // fetch children (results in array of objects, even if only one exists)
    922         $kids = get_posts($args);
    923         // check if empty
    924         $media = ( !empty($kids) ) ? $kids : null;
    925         return $media;
    926     }
    927 
    928     function session_manager() {
    929         if (!session_id()) {
    930             session_start();
    931         }
    932         $_SESSION['media'] = WP_MEDIA_DIR;
    933         $_SESSION['images'] = SOMA_IMG;
    934         global $current_user;
    935         $_SESSION['user'] = $current_user->data ? $current_user->roles[0] : 'guest';
    936     }
    937 
    938 
    939 
    940     //** checks for activation of plugins that somaFramework is dependent on ------------------------------------------------------//
    941     function check_plugin_dependency() {
    942         // require scribu's p2p plugin
    943         global $soma_options;
    944 
    945         if ( somaFunctions::fetch_index($soma_options, 'p2p') && !function_exists('p2p_register_connection_type') ) {
    946             add_action( 'admin_notices', create_function('', "
    947                 echo '<div id=\"message\" class=\"error\" style=\"font-weight: bold\"><p>PLUGIN REQUIRED: \"Posts 2 Posts\" - please <a href=\"http://scribu.net/wordpress/posts-to-posts\" target=\"_blank\">download</a> and/or activate!</p></div>';
    948             "));
    949         }
    950 
    951         // // require companion SOMA theme
    952         // $themes = get_themes();
    953         // if (!isset($themes['Stock Media Asset Manager'])) { // theme is not installed!
    954         //  add_action('admin_notices', 'theme_install_error_msg' );
    955         //  function theme_install_error_msg() {
    956         //      echo '<div id="message" class="error" style="font-weight: bold">';
    957         //      echo '<p>THEME MISSING: "Stock Media Asset Manager" - please <a href="' . network_admin_url() .'/themes.php">install!</a></p>';
    958         //      echo '</div>';
    959         //  }
    960         // }
    961         // if (get_current_theme() != 'Stock Media Asset Manager') { // theme is not activated!
    962         //  add_action('admin_notices', 'theme_active_error_msg' );
    963         //  function theme_active_error_msg() {
    964         //      echo '<div id="message" class="updated" style="font-weight: bold">';
    965         //      echo '<p>THEME NOT ACTIVE: "Stock Media Asset Manager" - please <a href="' . network_admin_url() .'/themes.php">activate!</a></p>';
    966         //      echo '</div>';
    967         //  }
    968         // }
    969     }
    970 
    971     //** generates full current url string with current query vars ------------------------------------------------------//
    972     function get_current_url() {
    973         $urlis = 'http://' . $_SERVER['HTTP_HOST'] . htmlentities($_SERVER['PHP_SELF']) . $_SERVER['REQUEST_URI'];
    974         return $urlis;
    975     }
    976 
    977     // displays warning error msg if wp version too old ------------------------------------------------------//
    978     function wp_version_check() {
    979         global $wp_version; #wp
    980         $new_admin_version = '4.4';
    981         $updateURL = "";
    982         if (version_compare($wp_version, $new_admin_version, '<')) {
    983             add_action( 'admin_notices', create_function('', "
    984                 echo '<div id=\"message\" class=\"error\" style=\"font-weight: bold\"><p>WORDPRESS 4.4 MINIMUM REQUIRED - please update WP or de-activate this plugin!</p></div>';
    985             "));
    986         }
    987     }
    988 
    989     /**
    990      * Displays (and returns) total number of posts within specified categories
    991      *
    992      * @param string $catslugs Comma separated category slugs
    993      * @param bool $display (optional) Echo the result?
    994      * @return int Post count
    995      */
    996     function in_category_count($catslugs = '', $display = true) {
    997         global $wpdb;
    998 
    999         $post_count = 0;
    1000         $slug_where = '';
    1001         $catslugs_arr = explode(',', $catslugs);
    1002 
    1003         foreach ($catslugs_arr as $catslugkey => $catslug) {
    1004             if ( $catslugkey > 0 ) {
    1005                 $slug_where .= ', ';
    1006              }
    1007 
    1008             $slug_where .= "'" . trim($catslug) . "'";
    1009         }
    1010 
    1011         $slug_where = "cat_terms.slug IN (" . $slug_where . ")";
    1012 
    1013         $sql =  "SELECT COUNT( DISTINCT cat_posts.ID ) AS post_count " .
    1014                 "FROM   " . $wpdb->term_taxonomy . " AS cat_term_taxonomy INNER JOIN " . $wpdb->terms . " AS cat_terms ON " .
    1015                             "cat_term_taxonomy.term_id = cat_terms.term_id " .
    1016                         "INNER JOIN " . $wpdb->term_relationships . " AS cat_term_relationships ON " .
    1017                             "cat_term_taxonomy.term_taxonomy_id = cat_term_relationships.term_taxonomy_id " .
    1018                         "INNER JOIN " . $wpdb->posts . " AS cat_posts ON " .
    1019                             "cat_term_relationships.object_id = cat_posts.ID " .
    1020                 "WHERE  cat_posts.post_status = 'publish' AND " .
    1021                         "cat_posts.post_type = 'post' AND " .
    1022                         "cat_term_taxonomy.taxonomy = 'category' AND " .
    1023                         $slug_where;
    1024 
    1025         $post_count = $wpdb->get_var($sql);
    1026 
    1027         if ( $display ) {
    1028             echo $post_count;
    1029         }
    1030 
    1031         return $post_count;
    1032     }
    1033 
    1034     // are we using this??
    1035     function get_posts_related_by_taxonomySQL($post_id, $taxonomy, $args=array()) {
    1036         global $wpdb;
    1037         $sql = <<<SQL
    1038             SELECT
    1039             related.object_id
    1040             FROM
    1041             {$wpdb->term_relationships} post
    1042             INNER JOIN {$wpdb->term_taxonomy} link ON post.term_taxonomy_id = link.term_taxonomy_id
    1043             INNER JOIN {$wpdb->term_relationships} related ON post.term_taxonomy_id = related.term_taxonomy_id
    1044             WHERE 1=1
    1045             AND link.taxonomy=$taxonomy
    1046             AND post.object_id=$post_id
    1047             AND post.object_id<>related.object_id
    1048             AND post.post_type==related.post_type
     680    //** retrieves featured image of a post, or an attachment itself and returns array of intermediate sizes, paths, urls ----------------------------------------------------------------------------------//
     681    public function fetch_featured_image($post = null, $specific = null) {
     682        if (is_wp_error($post)) return $post;
     683        if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
     684        if (is_object($post)) {
     685            $pid = $post->ID;
     686        } else {
     687            $pid = intval($post);
     688        }
     689
     690        $img = array(); // container
     691
     692        // fetch id of featured image attachment
     693        if (has_post_thumbnail($pid)) {
     694            $att_id = get_post_thumbnail_id($pid);
     695        }
     696
     697        // post is already attachment - just pass ID
     698        if (get_post_type($pid) == 'attachment') {
     699            $att_id = $pid;
     700        }
     701
     702        // some attachment successfully found
     703        if ($att_id) {
     704            $att_meta = wp_get_attachment_metadata($att_id);        // get metadata of attachment
     705            if ( !empty( $att_meta['subdir'] ) ) {                  // the original SMAM system generates this custom attachment meta when it created upload directories based on type/author. If it exists, use it
     706                $subdir = $att_meta['subdir'] . '/';
     707            }
     708            $dirname = dirname( $att_meta['file'] );                // when "Organize my uploads into month- and year-based folders" is turned on, the date subfolder is stored in file path
     709            if ( $dirname == "." ) {    // no subdirectory path is needed
     710                $subdir = "/";
     711            } else {
     712                $subdir = '/'. dirname($att_meta['file']) . '/';
     713            }
     714            // build the paths from the base media upload dir and the subdir
     715            $media_path = WP_MEDIA_DIR . $subdir;
     716            $media_url = WP_MEDIA_URL . $subdir;
     717
     718            // NEW ARRAY
     719            $img['id'] = $att_id;
     720
     721            // 'sizes' key will only exist if the uploaded image was equal or larger than the site option for thumbnail size
     722            if ( !is_null( somaFunctions::fetch_index( $att_meta, 'sizes' ) ) && !empty( $att_meta[ 'sizes' ] ) ) {
     723                // loop through all available image sizes, including any custom ones via add_image_size()
     724                foreach ($att_meta['sizes'] as $size => $data) {
     725                    $img[$size]['file']     =   $data['file'];
     726                    $img[$size]['height']   =   $data['height'];
     727                    $img[$size]['width']    =   $data['width'];
     728                    $img[$size]['url']      =   $media_url . $img[$size]['file'];
     729                    $img[$size]['path']     =   $media_path . $img[$size]['file'];
     730                }
     731                $img['full']['file']    =   basename($att_meta['file']);    // $att_meta['file'] contains the entire server path, so extract just the name
     732                $img['full']['url']     =   $media_url . $img['full']['file'];
     733                $img['full']['path']    =   $media_path . $img['full']['file'];
     734                $img['full']['height']  =   $att_meta['height'];
     735                $img['full']['width']   =   $att_meta['width'];
     736                $img['loc']['path']     =   $media_path;
     737                $img['loc']['url']      =   $media_url;
     738
     739            // populate thumb data with the direct file info, as the uploaded image was smaller or equal to the site option for thumbnail size. Yes, the thumb and the full img info are the same in this case...
     740            } else {
     741                $img['thumbnail']['file']   =   basename($att_meta['file']);
     742                $img['thumbnail']['height'] =   $att_meta['height'];
     743                $img['thumbnail']['width']  =   $att_meta['width'];
     744                $img['thumbnail']['url']    =   WP_MEDIA_URL . '/'. $att_meta['file'];  // don't include subdir when building paths, as we're taking path directly from 'file', which already includes it...
     745                $img['thumbnail']['path']   =   WP_MEDIA_DIR . '/'. $att_meta['file'];
     746                $img['full']['file']    =   basename($att_meta['file']);
     747                $img['full']['url']     =   WP_MEDIA_URL . '/'. $att_meta['file'];
     748                $img['full']['path']    =   WP_MEDIA_DIR . '/'. $att_meta['file'];
     749                $img['full']['height']  =   $att_meta['height'];
     750                $img['full']['width']   =   $att_meta['width'];
     751                $img['loc']['path']     =   WP_MEDIA_DIR;
     752                $img['loc']['url']      =   WP_MEDIA_URL;
     753
     754                //** FUTURE NOTE: might be good when there isn't a medium or large version of the image to default to some kind of "missing" image that actually says "image was too small"
     755            }
     756
     757            $img['mime']            =   get_post_mime_type($att_id);                                            // mime-type of file
     758            $img['date']            =   get_the_date('M j, Y',$att_id) ." - ". get_the_time('h:iA',$att_id);    // date attachment was created
     759            $img['orientation']     =   somaFunctions::fetch_index($att_meta, 'orientation');                   // custom attribute, generated with ACME system
     760            $img['original']        =   somaFunctions::fetch_index($att_meta, 'original');                      // custom attribute, generated with ACME system
     761
     762            // should we pass along? too confusing... use the typical meta fields below instead
     763            // $img['image_meta'] = $att_meta['image_meta'];
     764
     765            // need to fetch the post from the table to get all the extra fields
     766            $att_post = get_post($att_id);
     767            $img['title'] = $att_post->post_title;
     768            $img['description'] = $att_post->post_content;
     769            $img['caption'] = $att_post->post_excerpt;
     770            $img['alt'] = get_post_meta($att_id, '_wp_attachment_image_alt', true);
     771
     772        } else {
     773        // nothing found, return generic placeholder image
     774            $img['id'] = false;
     775            $sizes = get_intermediate_image_sizes();
     776            foreach ($sizes as $size) {
     777                $img[$size]['url'] = SOMA_IMG . 'placeholder-image.png';
     778            }
     779            $img['thumbnail']['width'] = SOMA_THUMB_WIDTH;
     780            $img['thumbnail']['height'] = SOMA_THUMB_HEIGHT;
     781            $img['full']['name'] = 'MISSING IMAGE';
     782            $img['full']['url'] = SOMA_IMG . 'placeholder-image.png';
     783            $img['file']['path'] = SOMA_DIR . 'images/placeholder-image.png';
     784            $img['file']['file'] = 'placeholder-image.png';
     785        }
     786
     787        // return just the requested URL
     788        if (!is_null($specific)) {
     789            if ($specific == 'filename') return $img['full']['file'];
     790            if (isset($att_meta['sizes']) && is_array($att_meta['sizes'])) {
     791                if (array_key_exists($specific, $img)) {
     792                    return $img[$specific]['url'];          // this allows for custom image sizes to be passed via $specific
     793                } else {
     794                    return $img['full']['url'];         // couldn't find the size requested, so just give original file
     795                }
     796            } else {
     797                return null;                                                            // no sizes have been generated for this attachment
     798            }
     799        } else {
     800            return $img;    // return array of variants
     801        }
     802
     803    }
     804
     805    // handles saving and retrieving post_meta via serialized arrays
     806    public function asset_meta( $action = null, $post = null, $key = null, $value = null, $serialize = null, $use_prefix = true ) {
     807        if (is_wp_error($post)) return $post;
     808        if ( empty($post) || empty($action) ) return new WP_Error('missing', "Must pass ID and action...");
     809        if (is_object($post)) {
     810            $pid = $post->ID;
     811        } else {
     812            $pid = intval($post);
     813        }
     814
     815
     816        global $soma_options;                                       // fetch options
     817        if ( $serialize === null ) {
     818            $serialize = somaFunctions::fetch_index($soma_options, 'meta_serialize');           // use default var if not passed in params
     819            if ($serialize == 1) {                                  // explicit true if evaluates as true
     820                $serialize = true;
     821            } else {
     822                $serialize = false;                                 // default false
     823            }
     824        }
     825        $prefix = $soma_options['meta_prefix'];
     826        // if we're supposed to use a prefix and we have one...
     827        if ( $use_prefix && !empty($prefix) ) {
     828            if ( $serialize ) {
     829                $meta_key =  $prefix . "_asset_meta";
     830            } else {
     831                $meta_key = $prefix . "_" . $key;
     832            }
     833        } else {
     834            // just use the given key, no prefix
     835            $meta_key = $key;
     836        }
     837
     838
     839
     840        switch ( $action ) {
     841            case ('save') :
     842                if (!$key) return new WP_Error('missing', "Must specify a field to save to...");
     843                if (!$value) return new WP_Error('missing', "Missing a value for $key...");
     844
     845                $post_meta = get_post_meta( $pid, $meta_key, true);         // retrieve meta
     846                if (empty($value)) {
     847                    if ( $serialize ) {
     848                        unset($post_meta[$key]);                            // remove key if value is empty (otherwise key will be saved with blank value)
     849                    } else {
     850                        return delete_post_meta( $pid, $meta_key );         // trash meta because value is empty
     851                    }
     852                } else {                                                    // set new value
     853                    if ( $serialize ) {
     854                        $post_meta[$key] = $value;
     855                    } else {
     856                        $post_meta = $value;
     857                    }
     858                }
     859                return update_post_meta( $pid, $meta_key, $post_meta );     // rewrite the array to post_meta
     860            break;
     861
     862            case ('get') :
     863                $post_meta = get_post_meta( $pid, $meta_key, true);         // retrieve meta
     864                if ( $serialize ) {
     865                    if (!$key) return $post_meta;                           // return whole meta array if no field specified
     866                    return $post_meta[$key];                                // return field
     867                } else {
     868                    return $post_meta;
     869                }
     870            break;
     871
     872            case ('delete') :
     873                // if (!$key) return delete_post_meta( $pid, $meta_key );   // note: this deletes the whole meta_key, all fields lost
     874                if (!$key) return new WP_Error('missing', "Missing a key...");
     875                $post_meta = get_post_meta( $pid, $meta_key, true);                 // retrieve meta
     876                if ( !empty( $post_meta ) ) {                                       // if there was data to be deleted, proceed
     877                    if ( $serialize ) {
     878                        unset($post_meta[$key]);                                    // trash meta
     879                        return update_post_meta( $pid, $meta_key, $post_meta );     // rewrite the array to post_meta
     880                    } else {
     881                        return delete_post_meta( $pid, $meta_key );                 // trash meta
     882                    }
     883                } else {                                                // nothing to delete, so don't bother (this happens when post gets saved with fields still blank, and the save_post functions attempt to clear the metadata)
     884                    return false;
     885                }
     886            break;
     887
     888            default :
     889                return new WP_Error('action', "First parameter must be 'save', 'get', or 'delete'...");
     890            break;
     891        }
     892    }
     893
     894
     895    // returns all attachments (except featured image)
     896    public function fetch_attached_media($post = null, $mime = null, $include_featured = false) {
     897        if (is_wp_error($post)) return $post;
     898        if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
     899        if (is_object($post)) {
     900            $pid = $post->ID;
     901        } else {
     902            $pid = intval($post);
     903        }
     904
     905        $args = array(
     906            'post_parent' => $pid,
     907            'post_type' => 'attachment',
     908            'numberposts' => -1,
     909            'post_status' => 'any',
     910            'orderby' => 'menu_order',
     911            'order' => 'ASC',
     912            'exclude' => get_post_thumbnail_id($pid),
     913        );
     914        if ($include_featured == true) {
     915            unset($args['exclude']);
     916        }
     917        if (!empty($mime)) {
     918            // only return requested media type (audio/mpeg, video/mp4, image/jpeg, application/pdf, application/zip)
     919            $args['post_mime_type'] = $mime;
     920        }
     921        // fetch children (results in array of objects, even if only one exists)
     922        $kids = get_posts($args);
     923        // check if empty
     924        $media = ( !empty($kids) ) ? $kids : null;
     925        return $media;
     926    }
     927
     928    function session_manager() {
     929        if (!session_id()) {
     930            session_start();
     931        }
     932        $_SESSION['media'] = WP_MEDIA_DIR;
     933        $_SESSION['images'] = SOMA_IMG;
     934        global $current_user;
     935        $_SESSION['user'] = $current_user->data ? $current_user->roles[0] : 'guest';
     936    }
     937
     938
     939
     940    //** checks for activation of plugins that somaFramework is dependent on ------------------------------------------------------//
     941    function check_plugin_dependency() {
     942        // require scribu's p2p plugin
     943        global $soma_options;
     944
     945        if ( somaFunctions::fetch_index($soma_options, 'p2p') && !function_exists('p2p_register_connection_type') ) {
     946            add_action( 'admin_notices', function() {
     947                echo '<div id=\"message\" class=\"error\" style=\"font-weight: bold\"><p>PLUGIN REQUIRED: \"Posts 2 Posts\" - please <a href=\"http://scribu.net/wordpress/posts-to-posts\" target=\"_blank\">download</a> and/or activate!</p></div>';
     948                }
     949            );
     950        }
     951
     952        // // require companion SOMA theme
     953        // $themes = get_themes();
     954        // if (!isset($themes['Stock Media Asset Manager'])) { // theme is not installed!
     955        //  add_action('admin_notices', 'theme_install_error_msg' );
     956        //  function theme_install_error_msg() {
     957        //      echo '<div id="message" class="error" style="font-weight: bold">';
     958        //      echo '<p>THEME MISSING: "Stock Media Asset Manager" - please <a href="' . network_admin_url() .'/themes.php">install!</a></p>';
     959        //      echo '</div>';
     960        //  }
     961        // }
     962        // if (get_current_theme() != 'Stock Media Asset Manager') { // theme is not activated!
     963        //  add_action('admin_notices', 'theme_active_error_msg' );
     964        //  function theme_active_error_msg() {
     965        //      echo '<div id="message" class="updated" style="font-weight: bold">';
     966        //      echo '<p>THEME NOT ACTIVE: "Stock Media Asset Manager" - please <a href="' . network_admin_url() .'/themes.php">activate!</a></p>';
     967        //      echo '</div>';
     968        //  }
     969        // }
     970    }
     971
     972    //** generates full current url string with current query vars ------------------------------------------------------//
     973    function get_current_url() {
     974        $urlis = 'http://' . $_SERVER['HTTP_HOST'] . htmlentities($_SERVER['PHP_SELF']) . $_SERVER['REQUEST_URI'];
     975        return $urlis;
     976    }
     977
     978    // displays warning error msg if wp version too old ------------------------------------------------------//
     979    function wp_version_check() {
     980        global $wp_version; #wp
     981        $new_admin_version = '4.4';
     982        $updateURL = "";
     983        if (version_compare($wp_version, $new_admin_version, '<')) {
     984            add_action( 'admin_notices', function() {
     985                echo '<div id=\"message\" class=\"error\" style=\"font-weight: bold\"><p>WORDPRESS 4.4 MINIMUM REQUIRED - please update WP or de-activate this plugin!</p></div>';
     986                }
     987            );
     988        }
     989    }
     990
     991    /**
     992     * Displays (and returns) total number of posts within specified categories
     993     *
     994     * @param string $catslugs Comma separated category slugs
     995     * @param bool $display (optional) Echo the result?
     996     * @return int Post count
     997     */
     998    function in_category_count($catslugs = '', $display = true) {
     999        global $wpdb;
     1000
     1001        $post_count = 0;
     1002        $slug_where = '';
     1003        $catslugs_arr = explode(',', $catslugs);
     1004
     1005        foreach ($catslugs_arr as $catslugkey => $catslug) {
     1006            if ( $catslugkey > 0 ) {
     1007                $slug_where .= ', ';
     1008             }
     1009
     1010            $slug_where .= "'" . trim($catslug) . "'";
     1011        }
     1012
     1013        $slug_where = "cat_terms.slug IN (" . $slug_where . ")";
     1014
     1015        $sql =  "SELECT COUNT( DISTINCT cat_posts.ID ) AS post_count " .
     1016                "FROM   " . $wpdb->term_taxonomy . " AS cat_term_taxonomy INNER JOIN " . $wpdb->terms . " AS cat_terms ON " .
     1017                            "cat_term_taxonomy.term_id = cat_terms.term_id " .
     1018                        "INNER JOIN " . $wpdb->term_relationships . " AS cat_term_relationships ON " .
     1019                            "cat_term_taxonomy.term_taxonomy_id = cat_term_relationships.term_taxonomy_id " .
     1020                        "INNER JOIN " . $wpdb->posts . " AS cat_posts ON " .
     1021                            "cat_term_relationships.object_id = cat_posts.ID " .
     1022                "WHERE  cat_posts.post_status = 'publish' AND " .
     1023                        "cat_posts.post_type = 'post' AND " .
     1024                        "cat_term_taxonomy.taxonomy = 'category' AND " .
     1025                        $slug_where;
     1026
     1027        $post_count = $wpdb->get_var($sql);
     1028
     1029        if ( $display ) {
     1030            echo $post_count;
     1031        }
     1032
     1033        return $post_count;
     1034    }
     1035
     1036    // are we using this??
     1037    function get_posts_related_by_taxonomySQL($post_id, $taxonomy, $args=array()) {
     1038        global $wpdb;
     1039        $sql = <<<SQL
     1040            SELECT
     1041            related.object_id
     1042            FROM
     1043            {$wpdb->term_relationships} post
     1044            INNER JOIN {$wpdb->term_taxonomy} link ON post.term_taxonomy_id = link.term_taxonomy_id
     1045            INNER JOIN {$wpdb->term_relationships} related ON post.term_taxonomy_id = related.term_taxonomy_id
     1046            WHERE 1=1
     1047            AND link.taxonomy=$taxonomy
     1048            AND post.object_id=$post_id
     1049            AND post.object_id<>related.object_id
     1050            AND post.post_type==related.post_type
    10491051SQL;
    1050     $post_ids = $wpdb->get_col($wpdb->prepare($sql));
    1051     $post = get_post($post_id);
    1052     $args = wp_parse_args($args,array(
    1053         'post_type' => $post->post_type,
    1054         'post__in' => $post_ids,
    1055     ));
    1056     var_dump($args);
    1057     return new WP_Query($args);
    1058     }
    1059 
    1060     function get_posts_related_by_taxonomy($post_id,$taxonomy,$args=array()) {
    1061         $query = new WP_Query();
    1062         $terms = wp_get_object_terms($post_id,$taxonomy);
    1063         if (count($terms)) {
    1064             // Assumes only one term for per post in this taxonomy
    1065             $post_ids = get_objects_in_term($terms[0]->term_id,$taxonomy);
    1066             $post = get_post($post_id);
    1067             $args = wp_parse_args($args,array(
    1068                 'post_type' => $post->post_type, // The assumes the post types match
    1069                 'post__in' => $post_ids,
    1070                 'post__not_in' => $post->ID,
    1071                 'taxonomy' => $taxonomy,
    1072                 'term' => $terms[0]->slug,
    1073                 ));
    1074             $query = new WP_Query($args);
    1075         }
    1076         return $query;
    1077     }
    1078 
    1079     // checks if date string is actually formatted as desired
    1080     function check_datetime_format($date = null, $format = "Y-m-d H:i:s") {
    1081         if ($date == null) return false;
    1082 
    1083         $date = trim($date);
    1084         $time = strtotime($date);
    1085 
    1086         $is_valid = date($format, $time) == $date;
    1087 
    1088         return $is_valid;
    1089     }
    1090 
    1091     function fetch_start_timestamp($pid) {
    1092         $date = somaFunctions::asset_meta('get', $pid, 'start_date');
    1093         $time = somaFunctions::asset_meta('get', $pid, 'start_time');
    1094         $start = strtotime($date . " " . $time);
    1095         return $start;
    1096     }
    1097 
    1098     function fetch_end_timestamp($pid) {
    1099         $date = somaFunctions::asset_meta('get', $pid, 'end_date');
    1100         $time = somaFunctions::asset_meta('get', $pid, 'end_time');
    1101         $end = strtotime($date . " " . $time);
    1102         return $end;
    1103     }
    1104 
    1105     // shows an indicator in the admin side menu for pending items
    1106     function show_pending_number( $menu ) {
    1107         // currently this function returns count for site-wide posts, not by author, so disable for non-staff
    1108         if (!SOMA_STAFF) return $menu;
    1109         //
    1110         $status = "pending";
    1111         $types = get_post_types( array( '_builtin' => false  ), 'names' );
    1112         foreach ($types as $type) {
    1113             $num_posts = wp_count_posts( $type, 'readable' );
    1114             $pending_count = 0;
    1115             if ( !empty($num_posts->$status) )
    1116                 $pending_count = $num_posts->$status;
    1117 
    1118             // build string to match in $menu array
    1119             $menu_str = 'edit.php?post_type=' . $type;
    1120 
    1121             // loop through $menu items, find match, add indicator
    1122             foreach( $menu as $menu_key => $menu_data ) {
    1123                 if( $menu_str != $menu_data[2] )
    1124                     continue;
    1125                 $menu[$menu_key][0] .= " <span class='update-plugins count-$pending_count'><span class='plugin-count'>" . number_format_i18n($pending_count) . '</span></span>';
    1126             }
    1127         }
    1128         return $menu;
    1129     }
    1130 
    1131     /**
    1132     * Kill WordPress execution and display HTML message with error message.
    1133     *
    1134     * @since 3.0.0
    1135     * @access private
    1136     *
    1137     * @param string $message Error message.
    1138     * @param string $title Error title.
    1139     * @param string|array $args Optional arguements to control behaviour.
    1140     * inspired by JPB_User_Caps
    1141     */
    1142     function soma_wp_die_handler( $message, $title = '', $args = array() ) {
    1143         var_dump(func_get_args());
    1144         $defaults = array( 'response' => 500 );
    1145         $r = wp_parse_args($args, $defaults);
    1146 
    1147         $have_gettext = function_exists('__');
    1148 
    1149         if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
    1150             if ( empty( $title ) ) {
    1151                 $error_data = $message->get_error_data();
    1152                 if ( is_array( $error_data ) && isset( $error_data['title'] ) )
    1153                     $title = $error_data['title'];
    1154             }
    1155             $errors = $message->get_error_messages();
    1156             switch ( count( $errors ) ) :
    1157             case 0 :
    1158                 $message = '';
    1159                 break;
    1160             case 1 :
    1161                 $message = "<p>{$errors[0]}</p>";
    1162                 break;
    1163             default :
    1164                 $message = "<ul>\n\t\t<li>" . join( "</li>\n\t\t<li>", $errors ) . "</li>\n\t</ul>";
    1165                 break;
    1166             endswitch;
    1167         } elseif ( is_string( $message ) ) {
    1168             $message = "<p>$message</p>";
    1169         }
    1170 
    1171         if ( isset( $r['back_link'] ) && $r['back_link'] ) {
    1172             $back_text = $have_gettext? __('&laquo; Back') : '&laquo; Back';
    1173             $message .= "\n<p><a href='javascript:history.back()'>$back_text</p>";
    1174         }
    1175 
    1176         if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL )
    1177             $admin_dir = WP_SITEURL . '/wp-admin/';
    1178         elseif ( function_exists( 'get_bloginfo' ) && '' != get_bloginfo( 'wpurl' ) )
    1179             $admin_dir = get_bloginfo( 'wpurl' ) . '/wp-admin/';
    1180         elseif ( strpos( $_SERVER['PHP_SELF'], 'wp-admin' ) !== false )
    1181             $admin_dir = '';
    1182         else
    1183             $admin_dir = 'wp-admin/';
    1184 
    1185         if ( !function_exists( 'did_action' ) || !did_action( 'admin_head' ) ) :
    1186         if ( !headers_sent() ) {
    1187             status_header( $r['response'] );
    1188             nocache_headers();
    1189             header( 'Content-Type: text/html; charset=utf-8' );
    1190         }
    1191 
    1192         if ( empty($title) )
    1193             $title = $have_gettext ? __('WordPress &rsaquo; Error') : 'WordPress &rsaquo; Error';
    1194 
    1195         $text_direction = 'ltr';
    1196         if ( isset($r['text_direction']) && 'rtl' == $r['text_direction'] )
    1197             $text_direction = 'rtl';
    1198         elseif ( function_exists( 'is_rtl' ) && is_rtl() )
    1199             $text_direction = 'rtl';
    1200     ?>
    1201     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    1202     <!-- Ticket #11289, IE bug fix: always pad the error page with enough characters such that it is greater than 512 bytes, even after gzip compression abcdefghijklmnopqrstuvwxyz1234567890aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz11223344556677889900abacbcbdcdcededfefegfgfhghgihihjijikjkjlklkmlmlnmnmononpopoqpqprqrqsrsrtstsubcbcdcdedefefgfabcadefbghicjkldmnoepqrfstugvwxhyz1i234j567k890laabmbccnddeoeffpgghqhiirjjksklltmmnunoovppqwqrrxsstytuuzvvw0wxx1yyz2z113223434455666777889890091abc2def3ghi4jkl5mno6pqr7stu8vwx9yz11aab2bcc3dd4ee5ff6gg7hh8ii9j0jk1kl2lmm3nnoo4p5pq6qrr7ss8tt9uuvv0wwx1x2yyzz13aba4cbcb5dcdc6dedfef8egf9gfh0ghg1ihi2hji3jik4jkj5lkl6kml7mln8mnm9ono -->
    1203     <html xmlns="http://www.w3.org/1999/xhtml" <?php if ( function_exists( 'language_attributes' ) ) language_attributes(); ?>>
    1204     <head>
    1205         <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    1206         <title><?php echo $title ?></title>
    1207         <link rel="stylesheet" href="<?php echo $admin_dir; ?>css/install.css" type="text/css" />
    1208     <?php
    1209     if ( 'rtl' == $text_direction ) : ?>
    1210         <link rel="stylesheet" href="<?php echo $admin_dir; ?>css/install-rtl.css" type="text/css" />
    1211     <?php endif; ?>
    1212     </head>
    1213     <body id="error-page">
    1214     <?php endif; ?>
    1215         <?php echo $message; ?>
    1216     </body>
    1217     </html>
    1218     <?php
    1219         die();
    1220     }
    1221 
    1222     //** end soma_wp_die_handler
    1223 
    1224     // Remove 'Administrator' from the list of roles if the current user is not an admin
    1225     function editable_roles( $roles ){
    1226         if( isset( $roles['administrator'] ) && !current_user_can('administrator') ){
    1227             unset( $roles['administrator']);
    1228         }
    1229         return $roles;
    1230     }
    1231 
    1232     // If someone is trying to edit or delete an admin and that user isn't an admin, don't allow it -- IS THIS STILL NEEDED?
    1233     function admin_map_meta_cap( $caps, $cap, $user_id, $args ){
    1234 
    1235         switch( $cap ){
    1236             case 'edit_user':
    1237             case 'remove_user':
    1238             case 'promote_user':
    1239                 if( isset($args[0]) && $args[0] == $user_id )
    1240                     break;
    1241                 elseif( !isset($args[0]) )
    1242                     $caps[]  = 'do_not_allow';
    1243                 $other    = new WP_User( absint($args[0]) );
    1244                 if( $other->has_cap( 'administrator' ) ){
    1245                     if(!current_user_can('administrator')){
    1246                         $caps[] = 'do_not_allow';
    1247                     }
    1248                 }
    1249             break;
    1250             case 'delete_user':
    1251             case 'delete_users':
    1252                 if( !isset($args[0]) )
    1253                     break;
    1254                 $other    = new WP_User( absint($args[0]) );
    1255                 if( $other->has_cap( 'administrator' ) ){
    1256                     if(!current_user_can('administrator')){
    1257                         $caps[] = 'do_not_allow';
    1258                     }
    1259                 }
    1260             break;
    1261             default:
    1262             break;
    1263         }
    1264         return $caps;
    1265     }
    1266 
    1267     // AJAX delete images on the fly.
    1268     // can't use nonce checking because the elements calling this function are dynamically created in javascript
    1269     function ajax_unlink_file() {
    1270         $file = somaFunctions::fetch_index($_POST, 'data');     // retrieve file from ajax post
    1271         if ($file && file_exists($file)) {                      // make sure it's there
    1272             $delete = unlink($file);                            // kill it
    1273             if ($delete) {
    1274                 $response = array( 'success' => true, 'msg' => 'Successfully deleted the uploaded file!' );
    1275             } else {
    1276                 $response = array( 'error' => true, 'msg' => 'Could not delete the uploaded file...' );
    1277             }
    1278         } else {
    1279             $response = array( 'error' => true, 'msg' => 'Could not find the file to delete...' );
    1280         }
    1281         echo json_encode( $response );
    1282         exit;
    1283     }
    1284 
    1285     // AJAX delete attachments
    1286     function ajax_delete_attachment() {
    1287         $nonce = check_ajax_referer( 'soma-delete-attachment', 'nonce', false );
    1288         if (!$nonce) {
    1289             $response = array( 'error' => true, 'msg' => 'nonce verification failed...' );
    1290             echo json_encode( $response );
    1291             exit;
    1292         }
    1293         $att_id = somaFunctions::fetch_index($_POST, 'data');
    1294         if ($att_id) {
    1295             $result = wp_delete_attachment($att_id, true);
    1296             $response = array( 'success' => true, 'msg' => $result );
    1297         } else {
    1298             $response = array( 'error' => true, 'msg' => 'Could not find the attachment...' );
    1299         }
    1300         echo json_encode( $response );
    1301         exit;
    1302     }
    1303 
    1304 
    1305 
    1306     // grabs metadata from public vimeo API, returns array
    1307     function fetch_vimeo_meta( $link = null ) {
    1308         if ( !$link ) return new WP_Error('missing', "Must specify a video ID");
    1309         // use the oembed API to parse the raw URL for us
    1310         $oembed = json_decode(file_get_contents("http://vimeo.com/api/oembed.json?url=".urlencode($link)), true);
    1311         $meta = array_shift(unserialize(file_get_contents("http://vimeo.com/api/v2/video/{$oembed['video_id']}.php")));
    1312         $meta = array_merge($oembed, $meta);
    1313         return $meta;
    1314     }
    1315 
    1316     // grabs metadata from public youtube API, returns array
    1317     function fetch_youtube_meta( $url = null ) {
    1318         if ( !$url ) return new WP_Error('missing', "Must specify a video ID");
    1319         // extract ID
    1320         if (preg_match('%(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) {
    1321             $id = $match[1];
    1322         }
    1323         $meta = json_decode(file_get_contents("http://gdata.youtube.com/feeds/api/videos/$id?v=2&alt=json"), true);     // json_decode true returns data as array instead of object
    1324         $meta = $meta['entry'];     // isolate the single entry we requested
    1325         return $meta;
    1326     }
    1327 
    1328     // grab metadata from external site URL
    1329     // only supporting 3 media sites right now: youtube, vimeo, soundcloud
    1330     function fetch_external_media($url = null, $width = null, $height = null) {
    1331         if ( !$url ) return new WP_Error('missing', "Must specify a valid URL from a supported site");
    1332 
    1333         // in case user didn't include http
    1334         if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {
    1335             $url = "http://" . $url;
    1336         }
    1337 
    1338         // identify which site
    1339         switch (true) {
    1340             case ( strpos( $url, 'youtube.com' ) > 0 || strpos ($url, 'youtu.be' ) > 0 ) :
    1341                 $site = 'youtube';
    1342             break;
    1343             case ( strpos( $url, 'vimeo.com' ) > 0 ) :
    1344                 $site = 'vimeo';
    1345             break;
    1346             case ( strpos( $url, 'soundcloud.com' ) > 0 ) :
    1347                 $site = 'soundcloud';
    1348             break;
    1349             default:
    1350                 return new WP_Error('missing', "Problem with the URL. Can't figure out which site it's from (or it's not supported). Check to make sure you entered it correctly...");
    1351             break;
    1352         }
    1353 
    1354 
    1355         $media = array();   // init container
    1356         $width = $width ? $width : "853";           // iframe default
    1357         $height = $height ? $height : "480";        // iframe default
    1358 
    1359         // extract ID, fetch meta, build output
    1360         switch ($site) {
    1361             case "youtube";
    1362                 if (preg_match('%(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) {
    1363                     $id = $match[1];
    1364                     ob_start();
    1365                         ini_set('user_agent','Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17');
    1366                         // $meta = file_get_contents("http://gdata.youtube.com/feeds/api/videos/".$id."?v=2&alt=json");  // old, don't work??
    1367                         $meta = file_get_contents("https://www.youtube.com/oembed?url=".$url."&format=json");
    1368                         $meta = json_decode($meta, true);   // json_decode true returns data as array instead of object
    1369                     ob_end_clean();
    1370                    
    1371                     $media['url']       = "http://www.youtube.com/watch?v=$id";                                                                 // rebuild external url
    1372                     $media['id']        = $id;
    1373                     $media['site']      = $site;
    1374                     $media['thumb']     = $meta['thumbnail_url'];                                               // grabs url of medium 360x480 image
    1375                     $media['title']     = $meta['title'];
    1376                     // $media['desc']       = stripslashes($meta['media$group']['media$description']['$t']);
    1377                     // $media['duration']   = $meta['media$group']['media$content'][0]['duration'];
    1378                     $media['mobile']    = "youtube:$id";                                                                                    // launches youtube app, i think...
    1379                     $media['embed']     = "<iframe width=\"$width\" height=\"$height\" src=\"http://www.youtube.com/embed/$id?rel=0&hd=1\" frameborder=\"0\" allowfullscreen></iframe>";
    1380                     $media['iframe']    = "http://www.youtube.com/embed/$id?rel=0&hd=1&autoplay=1";                                         // url to pass to iframe renderers
    1381                     // $media['api']        = $meta;                                                                                            // we include everything we got from the site API, just in case (each site formats differently)
    1382                 } else {
    1383                     return new WP_Error('missing', "Can't extract ID from youtube URL...");
    1384                 }
    1385             break;
    1386             case "vimeo";
    1387                 // try to get around 403 forbidden errors when using file_get_contents
    1388                 ob_start();
    1389                     ini_set('user_agent','Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17');
    1390                     $oembed = file_get_contents("https://vimeo.com/api/oembed.json?url=".urlencode($url));
    1391                     $oembed = json_decode($oembed, true);
    1392                 ob_end_clean();
    1393                 if (!empty($oembed)) {              // get oembed metadata
    1394                     $id = $oembed['video_id'];                                                                                              // extract id from api response
    1395                     ob_start();
    1396                         ini_set('user_agent','Mozilla/4.0 (compatible; MSIE 6.0)');
    1397                         $meta = file_get_contents("https://vimeo.com/api/v2/video/$id.php");
    1398                     ob_end_clean();
    1399                     $meta = array_shift(unserialize($meta));                            // get more metadata (note: we're making two distinct calls to API for vimeo)
    1400                     $meta = array_merge($oembed, $meta);                                                                                    // combine the two (including some overlapping keys)
    1401                     $media['url']       = "http://vimeo.com/$id";                                                                           // rebuild external url
    1402                     $media['id']        = $id;                                                                                              // store raw media ID
    1403                     $media['site']      = $site;                                                                                            //
    1404                     $media['thumb']     = $meta['thumbnail_url'];                                                                           // grabs url of default image 640x360
    1405                     $media['title']     = stripslashes($meta['title']);                                                                                 //
    1406                     $media['desc']      = stripslashes($meta['description']);                                                                               //
    1407                     $media['duration']  = $meta['duration'];                                                                                // in seconds
    1408                     $media['mobile']    = $meta['mobile_url'];                                                                              // mobile-friendly display url
    1409                     $media['embed']     = $meta['html'];                                                                                    // auto embed code (should be HTML5/ipad compatible)
    1410                     $media['iframe']    = "http://player.vimeo.com/video/$id?title=0&amp;byline=0&amp;portrait=0&amp;&amp;frameborder=0&amp;autoplay=1";            // url to pass to iframe renderers (like colorbox)
    1411                     $media['api']       = $meta;                                                                                            // we include everything we got from the site API, just in case (each site formats differently)
    1412                 } else {
    1413                     return new WP_Error('missing', "Can't extract ID from vimeo URL...");
    1414                 }
    1415             break;
    1416             case "soundcloud";
    1417                 $clientid = "006b3d9b3bbd5bd6cc7d27ab05d9a11b";     // my soundcloud api id
    1418                 if ($meta = json_decode(file_get_contents("http://api.soundcloud.com/resolve?client_id=".$clientid."&format=json&url=".urlencode($url)), true)) {
    1419                 //  $oembed = json_decode(file_get_contents("http://soundcloud.com/oembed?format=json&show_comments=false&auto_play=true&url=".urlencode($url)), true);
    1420                     $media['id']        = $meta['id'];
    1421                     $media['url']       = $meta['permalink_url'];
    1422                     $media['site']      = $site;
    1423                     if (empty($meta['artwork_url'])) {
    1424                         $media['thumb'] = $meta['waveform_url'];
    1425                     } else {
    1426                         $media['thumb'] = $meta['artwork_url'];
    1427                     }
    1428                     $media['title']     = $meta['title'];
    1429                     $media['desc']      = $meta['description'];
    1430                     $media['duration']  = $meta['duration'];
    1431                     $media['mobile']    = $meta['stream_url']."?client_id=006b3d9b3bbd5bd6cc7d27ab05d9a11b";
    1432                     $media['direct']    = $meta['stream_url']."?client_id=006b3d9b3bbd5bd6cc7d27ab05d9a11b";
    1433                     $media['iframe']    = "http://w.soundcloud.com/player/?url=".$meta['uri']."?auto_play=true&show_artwork=false&show_comments=false&color=000000&show_playcount=false&sharing=false&show_user=false&liking=false";                // http://developers.soundcloud.com/docs/oembed   --  https://github.com/soundcloud/Widget-JS-API/wiki/widget-options
    1434                     $media['embed']     = '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="'.$media['iframe'].'"></iframe>';
    1435                     $media['format']    = $meta['original_format'];
    1436                     $media['api']       = $meta;                                                    // mp3 in this case
    1437                 } else {
    1438                     return new WP_Error('missing', "Can't extract ID from soundcloud URL...");
    1439                 }
    1440             break;
    1441         }
    1442 
    1443         if (empty($media)) return new WP_Error('missing', "Somehow we failed...");
    1444         // success
    1445         return $media;
    1446     }
    1447 
    1448 
    1449     // retrieves ID of attachment from given URL
    1450     // source: Rarst  http://wordpress.stackexchange.com/questions/6645/turn-a-url-into-an-attachment-post-id
    1451     function get_attachment_id_from_url( $url ) {
    1452 
    1453         $dir = wp_upload_dir();
    1454         $dir = trailingslashit($dir['baseurl']);
    1455 
    1456         if( false === strpos( $url, $dir ) )
    1457             return new WP_Error('missing', "Something wrong with this url...");
    1458 
    1459         $file = basename($url);
    1460 
    1461         $query = array(
    1462             'post_type' => 'attachment',
    1463             'fields' => 'ids',
    1464             'meta_query' => array(
    1465                 array(
    1466                     'value' => $file,
    1467                     'compare' => 'LIKE',
    1468                     )
    1469             )
    1470         );
    1471 
    1472         $query['meta_query'][0]['key'] = '_wp_attached_file';
    1473         $ids = get_posts( $query );
    1474 
    1475         foreach( $ids as $id )
    1476             if ( $url == array_shift( wp_get_attachment_image_src($id, 'full') ) )
    1477                 return $id;
    1478 
    1479         $query['meta_query'][0]['key'] = '_wp_attachment_metadata';
    1480         $ids = get_posts( $query );
    1481 
    1482         foreach( $ids as $id ) {
    1483 
    1484             $meta = wp_get_attachment_metadata($id);
    1485 
    1486             foreach( $meta['sizes'] as $size => $values )
    1487             if( $values['file'] == $file && $url == array_shift( wp_get_attachment_image_src($id, $size) ) ) {
    1488 
    1489                 return $id;
    1490             }
    1491         }
    1492 
    1493         return new WP_Error('missing', "Somehow we failed...");
    1494     }
    1495 
    1496     /**
    1497     * Download an image from the specified URL and attach it to a post.
    1498     * Modified version of core function media_sideload_image() in /wp-admin/includes/media.php  (which returns an html img tag instead of attachment ID)
    1499     * Additional functionality: ability override actual filename, set as post thumbnail, and to pass $post_data to override values in wp_insert_attachment (original only allowed $desc)
    1500     *
    1501     * @since 1.4
    1502     *
    1503     * @param string $url (required) The URL of the image to download
    1504     * @param int $post_id (required) The post ID the media is to be associated with
    1505     * @param bool $thumb (optional) Whether to make this attachment the Featured Image for the post
    1506     * @param string $filename (optional) Replacement filename for the URL filename (do not include extension)
    1507     * @param array $post_data (optional) Array of key => values for wp_posts table (ex: 'post_title' => 'foobar', 'post_status' => 'draft')
    1508     * @return int|object The ID of the attachment or a WP_Error on failure
    1509     */
    1510     function attach_external_image( $url = null, $post_id = null, $thumb = null, $filename = null, $post_data = array() ) {
    1511         if ( !$url || !$post_id ) return new WP_Error('missing', "Need a valid URL and post ID...");
    1512         if ( !self::array_is_associative( $post_data ) ) return new WP_Error('missing', "Must pass post data as associative array...");
    1513 
    1514         // Download file to temp location, returns full server path to temp file, ex; /home/somatics/public_html/mysite/wp-content/26192277_640.tmp MUST BE FOLLOWED WITH AN UNLINK AT SOME POINT
    1515         $tmp = download_url( $url );
    1516 
    1517         // If error storing temporarily, unlink
    1518         if ( is_wp_error( $tmp ) ) {
    1519             @unlink($file_array['tmp_name']);   // clean up
    1520             $file_array['tmp_name'] = '';
    1521             return $tmp; // output wp_error
    1522         }
    1523 
    1524         preg_match('/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $url, $matches);    // fix file filename for query strings
    1525         $url_filename = basename($matches[0]);                                                  // extract filename from url for title
    1526         $url_type = wp_check_filetype($url_filename);                                           // determine file type (ext and mime/type)
    1527 
    1528         // override filename if given, reconstruct server path
    1529         if ( !empty( $filename ) ) {
    1530             $filename = sanitize_file_name($filename);
    1531             $tmppath = pathinfo( $tmp );                                                        // extract path parts
    1532             $new = $tmppath['dirname'] . "/". $filename . "." . $tmppath['extension'];          // build new path
    1533             rename($tmp, $new);                                                                 // renames temp file on server
    1534             $tmp = $new;                                                                        // push new filename (in path) to be used in file array later
    1535         }
    1536 
    1537         // assemble file data (should be built like $_FILES since wp_handle_sideload() will be using)
    1538         $file_array['tmp_name'] = $tmp;                                                         // full server path to temp file
    1539 
    1540         if ( !empty( $filename ) ) {
    1541             $file_array['name'] = $filename . "." . $url_type['ext'];                           // user given filename for title, add original URL extension
    1542         } else {
    1543             $file_array['name'] = $url_filename;                                                // just use original URL filename
    1544         }
    1545 
    1546         // set additional wp_posts columns
    1547         if ( empty( $post_data['post_title'] ) ) {
    1548             $post_data['post_title'] = basename($url_filename, "." . $url_type['ext']);         // just use the original filename (no extension)
    1549         }
    1550 
    1551         // make sure gets tied to parent
    1552         if ( empty( $post_data['post_parent'] ) ) {
    1553             $post_data['post_parent'] = $post_id;
    1554         }
    1555 
    1556         // required libraries for media_handle_sideload
    1557         require_once(ABSPATH . 'wp-admin/includes/file.php');
    1558         require_once(ABSPATH . 'wp-admin/includes/media.php');
    1559         require_once(ABSPATH . 'wp-admin/includes/image.php');
    1560 
    1561         // do the validation and storage stuff
    1562         $att_id = media_handle_sideload( $file_array, $post_id, null, $post_data );             // $post_data can override the items saved to wp_posts table, like post_mime_type, guid, post_parent, post_title, post_content, post_status
    1563 
    1564         // If error storing permanently, unlink
    1565         if ( is_wp_error($att_id) ) {
    1566             @unlink($file_array['tmp_name']);   // clean up
    1567             return $att_id; // output wp_error
    1568         }
    1569 
    1570         // set as post thumbnail if desired
    1571         if ($thumb) {
    1572             set_post_thumbnail($post_id, $att_id);
    1573         }
    1574 
    1575         return $att_id;
    1576     }
    1577 
    1578     /**
    1579     * Gets the excerpt of a specific post ID or object - if one doesn't exist, it will create one dynamically
    1580     * @since 1.7.3
    1581     *
    1582     * @param - $post - object/int/string - the ID or object of the post to get the excerpt of
    1583     * @param - $length - int - the length of the excerpt in words
    1584     * @param - $tags - string - the allowed HTML tags. These will not be stripped out
    1585     * @param - $extra - string - text to append to the end of the excerpt
    1586     */
    1587     function fetch_excerpt($post, $length = 30, $tags = '<a><em><strong>', $extra = '…') {
    1588 
    1589         if (is_wp_error($post)) return $post;
    1590         if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
    1591         if (!is_object($post)) {
    1592             $post = get_post(intval($post));
    1593         }
    1594         if (!is_object($post)) {
    1595             return new WP_Error('error', "Can't retrieve the requested post...");
    1596         }
    1597 
    1598         if (has_excerpt($post->ID)) {
    1599             $the_excerpt = $post->post_excerpt;
    1600             return apply_filters('the_content', $the_excerpt);
    1601         } else {
    1602             $the_excerpt = $post->post_content;
    1603         }
    1604 
    1605         $the_excerpt = strip_shortcodes(strip_tags($the_excerpt), $tags);
    1606         $the_excerpt = preg_split('/\b/', $the_excerpt, $length * 2+1);
    1607         $excerpt_waste = array_pop($the_excerpt);
    1608         $the_excerpt = implode($the_excerpt);
    1609         $the_excerpt .= $extra;
    1610 
    1611         return apply_filters('the_content', $the_excerpt);
    1612     }
    1613 
    1614     /**
    1615     * generates HTML link code in conjunction with the soma_go_redirect option
    1616     * won't work unless you've already hooked the soma_go_redirect_codes filter and added a slug/url pair
    1617     * @since 1.7.6
    1618     *
    1619     * @param - $slug - string - properly formatted slug for the url: /go/[slug]
    1620     * @param - $text - string - text to be wrapped inside the <a> tags
    1621     * @return - string - HTML link code
    1622     */
    1623     function make_go_link($slug = null, $text = null) {
    1624         if (empty($slug) || empty($text)) return new WP_Error('missing', "must pass a slug and text argument!");
    1625         $slug = sanitize_title_with_dashes($slug);
    1626         $text = sanitize_title($text);
    1627         $codes = apply_filters('soma_go_redirect_codes', $codes);
    1628         if (array_key_exists($slug, $codes)) {
    1629             return "<a href='{$codes[$slug]}' rel='nofollow'>$text</a>";
    1630         } else {
    1631             return new WP_Error('error', "that slug hasn't been added yet, so I can't give you a URL ...");
    1632         }
    1633 
    1634     }
    1635 
    1636     // Returns array of details about a file (only works on attachments)
    1637     // -------------------------------------------------------------
    1638     function fetch_file($pid = null) {
    1639         if ( empty( $pid ) ) return false;
    1640         $type = get_post_type( intval( $pid ) );
    1641         if ( $type != "attachment" ) return false;
    1642 
    1643         $file_path = get_attached_file( $pid );
    1644         $file = pathinfo( $file_path );                                             // returns array of dirname, basename, extension, filename
    1645         $file['mime'] = get_post_mime_type( $pid );                                 // returns mime type
    1646         $file['url'] = wp_get_attachment_url( $pid );                               // returns full URL for downloading
    1647         $file['secure'] = self::build_request_link('legacy-download', $pid);        // returns secure download URL
    1648         if ( is_file( SOMA_DIR . "images/file-icons/" . $file['extension'] . ".png" ) ) {
    1649             $file['icon'] = SOMA_IMG . 'file-icons/'. $file['extension'].'.png';
    1650         } else {
    1651             $file['icon'] = SOMA_IMG . 'file-icons/bin.png';
    1652         }
    1653         $file['time'] = get_post_time("U", true, $pid);
    1654         return $file;
    1655     }
    1656 
    1657     // Return all sub pages of a given post
    1658     // -------------------------------------------------------------
    1659     function fetch_sub_pages($pid = null) {
    1660         if (empty($pid)) return null;
    1661         $post_type = get_post_type($pid);
    1662         $args = array(
    1663             'post_type' => $post_type,
    1664             'child_of' => $pid,
    1665             'parent' => $pid,
    1666             'sort_order' => 'ASC',
    1667             'sort_column' => 'menu_order',
    1668             'numberposts' => -1
    1669         );
    1670         return get_pages($args);
    1671     }
    1672 
    1673     // Return all root (top-level) posts of any given post type
    1674     // -------------------------------------------------------------
    1675     function fetch_root_pages($post_type = 'page') {
    1676         $args = array(
    1677             'post_type' => $post_type,
    1678             'parent' => 0,
    1679             'order' => 'ASC',
    1680             'orderby' => 'menu_order',
    1681             'numberposts' => -1
    1682         );
    1683         return get_pages($args);
    1684     }
    1685 
    1686     /**
    1687     * Helps in use of dash and underscore in field ID's of richtext field types (dealing with wp_editor() finicky rules)
    1688     * @since 1.8.1
    1689     *
    1690     * @param - $id - string - user defined ID for this field, anything that's not a lowercase letter or a dash or underscore will get trashed!!
    1691     * @return - $sanID - string - safe ID with obfuscations
    1692     */
    1693 
    1694     function sanitize_wpeditor_id($id) {
    1695         $sanID = preg_replace('/_+/', 'uuuuu', $id);            // to preserve some backwards compatibility, we'll make special replacements for dashes and underscores, so they'll get preserved
    1696         $sanID = preg_replace('/-+/', 'ddddd', $sanID);
    1697         $sanID = preg_replace('/[^a-z]+/', '', $sanID);         // final pass to eliminate all non-alpha characters
    1698         return $sanID;
    1699     }
    1700 
    1701 
    1702     function unsanitize_wpeditor_id($id) {
    1703         $unsanID = preg_replace('/uuuuu+/', '_', $id);          // to preserve some backwards compatibility, we'll make special replacements for dashes and underscores, so they'll get preserved
    1704         $unsanID = preg_replace('/ddddd+/', '-', $unsanID);
    1705         return $unsanID;
    1706     }
    1707 
    1708 
    1709     /**
    1710     * Constructs URL string to be used to trigger the soma_request_ action hook of the somaRequest Class
    1711     * typically used to pass an array of data to a custom function
    1712     *
    1713     * @since 1.8.5
    1714     *
    1715     * @param - $action - (string) identifier used to assemble the action hook
    1716     * @return - $data - (string/array) - stuff to be sent to the hooked function
    1717     */
    1718 
    1719     function build_request_link($action = null, $data = null) {
    1720         if (empty($action) || empty($data)) return new WP_Error('error', 'action or data missing for the request!');
    1721         $safedata = http_build_query(array('data' => $data));           // prepare data contents (even associative or indexed arrays) for inclusion in the URL
    1722         $link = get_option('siteurl') . "?soma_request=$action&$safedata&security=" . wp_create_nonce( "soma-request" );
    1723         return $link;
    1724     }
     1052    $post_ids = $wpdb->get_col($wpdb->prepare($sql));
     1053    $post = get_post($post_id);
     1054    $args = wp_parse_args($args,array(
     1055        'post_type' => $post->post_type,
     1056        'post__in' => $post_ids,
     1057    ));
     1058    var_dump($args);
     1059    return new WP_Query($args);
     1060    }
     1061
     1062    function get_posts_related_by_taxonomy($post_id,$taxonomy,$args=array()) {
     1063        $query = new WP_Query();
     1064        $terms = wp_get_object_terms($post_id,$taxonomy);
     1065        if (count($terms)) {
     1066            // Assumes only one term for per post in this taxonomy
     1067            $post_ids = get_objects_in_term($terms[0]->term_id,$taxonomy);
     1068            $post = get_post($post_id);
     1069            $args = wp_parse_args($args,array(
     1070                'post_type' => $post->post_type, // The assumes the post types match
     1071                'post__in' => $post_ids,
     1072                'post__not_in' => $post->ID,
     1073                'taxonomy' => $taxonomy,
     1074                'term' => $terms[0]->slug,
     1075                ));
     1076            $query = new WP_Query($args);
     1077        }
     1078        return $query;
     1079    }
     1080
     1081    // checks if date string is actually formatted as desired
     1082    function check_datetime_format($date = null, $format = "Y-m-d H:i:s") {
     1083        if ($date == null) return false;
     1084
     1085        $date = trim($date);
     1086        $time = strtotime($date);
     1087
     1088        $is_valid = date($format, $time) == $date;
     1089
     1090        return $is_valid;
     1091    }
     1092
     1093    function fetch_start_timestamp($pid) {
     1094        $date = somaFunctions::asset_meta('get', $pid, 'start_date');
     1095        $time = somaFunctions::asset_meta('get', $pid, 'start_time');
     1096        $start = strtotime($date . " " . $time);
     1097        return $start;
     1098    }
     1099
     1100    function fetch_end_timestamp($pid) {
     1101        $date = somaFunctions::asset_meta('get', $pid, 'end_date');
     1102        $time = somaFunctions::asset_meta('get', $pid, 'end_time');
     1103        $end = strtotime($date . " " . $time);
     1104        return $end;
     1105    }
     1106
     1107    // shows an indicator in the admin side menu for pending items
     1108    function show_pending_number( $menu ) {
     1109        // currently this function returns count for site-wide posts, not by author, so disable for non-staff
     1110        if (!SOMA_STAFF) return $menu;
     1111        //
     1112        $status = "pending";
     1113        $types = get_post_types( array( '_builtin' => false  ), 'names' );
     1114        foreach ($types as $type) {
     1115            $num_posts = wp_count_posts( $type, 'readable' );
     1116            $pending_count = 0;
     1117            if ( !empty($num_posts->$status) )
     1118                $pending_count = $num_posts->$status;
     1119
     1120            // build string to match in $menu array
     1121            $menu_str = 'edit.php?post_type=' . $type;
     1122
     1123            // loop through $menu items, find match, add indicator
     1124            foreach( $menu as $menu_key => $menu_data ) {
     1125                if( $menu_str != $menu_data[2] )
     1126                    continue;
     1127                $menu[$menu_key][0] .= " <span class='update-plugins count-$pending_count'><span class='plugin-count'>" . number_format_i18n($pending_count) . '</span></span>';
     1128            }
     1129        }
     1130        return $menu;
     1131    }
     1132
     1133    /**
     1134    * Kill WordPress execution and display HTML message with error message.
     1135    *
     1136    * @since 3.0.0
     1137    * @access private
     1138    *
     1139    * @param string $message Error message.
     1140    * @param string $title Error title.
     1141    * @param string|array $args Optional arguements to control behaviour.
     1142    * inspired by JPB_User_Caps
     1143    */
     1144    function soma_wp_die_handler( $message, $title = '', $args = array() ) {
     1145        var_dump(func_get_args());
     1146        $defaults = array( 'response' => 500 );
     1147        $r = wp_parse_args($args, $defaults);
     1148
     1149        $have_gettext = function_exists('__');
     1150
     1151        if ( function_exists( 'is_wp_error' ) && is_wp_error( $message ) ) {
     1152            if ( empty( $title ) ) {
     1153                $error_data = $message->get_error_data();
     1154                if ( is_array( $error_data ) && isset( $error_data['title'] ) )
     1155                    $title = $error_data['title'];
     1156            }
     1157            $errors = $message->get_error_messages();
     1158            switch ( count( $errors ) ) :
     1159            case 0 :
     1160                $message = '';
     1161                break;
     1162            case 1 :
     1163                $message = "<p>{$errors[0]}</p>";
     1164                break;
     1165            default :
     1166                $message = "<ul>\n\t\t<li>" . join( "</li>\n\t\t<li>", $errors ) . "</li>\n\t</ul>";
     1167                break;
     1168            endswitch;
     1169        } elseif ( is_string( $message ) ) {
     1170            $message = "<p>$message</p>";
     1171        }
     1172
     1173        if ( isset( $r['back_link'] ) && $r['back_link'] ) {
     1174            $back_text = $have_gettext? __('&laquo; Back') : '&laquo; Back';
     1175            $message .= "\n<p><a href='javascript:history.back()'>$back_text</p>";
     1176        }
     1177
     1178        if ( defined( 'WP_SITEURL' ) && '' != WP_SITEURL )
     1179            $admin_dir = WP_SITEURL . '/wp-admin/';
     1180        elseif ( function_exists( 'get_bloginfo' ) && '' != get_bloginfo( 'wpurl' ) )
     1181            $admin_dir = get_bloginfo( 'wpurl' ) . '/wp-admin/';
     1182        elseif ( strpos( $_SERVER['PHP_SELF'], 'wp-admin' ) !== false )
     1183            $admin_dir = '';
     1184        else
     1185            $admin_dir = 'wp-admin/';
     1186
     1187        if ( !function_exists( 'did_action' ) || !did_action( 'admin_head' ) ) :
     1188        if ( !headers_sent() ) {
     1189            status_header( $r['response'] );
     1190            nocache_headers();
     1191            header( 'Content-Type: text/html; charset=utf-8' );
     1192        }
     1193
     1194        if ( empty($title) )
     1195            $title = $have_gettext ? __('WordPress &rsaquo; Error') : 'WordPress &rsaquo; Error';
     1196
     1197        $text_direction = 'ltr';
     1198        if ( isset($r['text_direction']) && 'rtl' == $r['text_direction'] )
     1199            $text_direction = 'rtl';
     1200        elseif ( function_exists( 'is_rtl' ) && is_rtl() )
     1201            $text_direction = 'rtl';
     1202    ?>
     1203    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     1204    <!-- Ticket #11289, IE bug fix: always pad the error page with enough characters such that it is greater than 512 bytes, even after gzip compression abcdefghijklmnopqrstuvwxyz1234567890aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz11223344556677889900abacbcbdcdcededfefegfgfhghgihihjijikjkjlklkmlmlnmnmononpopoqpqprqrqsrsrtstsubcbcdcdedefefgfabcadefbghicjkldmnoepqrfstugvwxhyz1i234j567k890laabmbccnddeoeffpgghqhiirjjksklltmmnunoovppqwqrrxsstytuuzvvw0wxx1yyz2z113223434455666777889890091abc2def3ghi4jkl5mno6pqr7stu8vwx9yz11aab2bcc3dd4ee5ff6gg7hh8ii9j0jk1kl2lmm3nnoo4p5pq6qrr7ss8tt9uuvv0wwx1x2yyzz13aba4cbcb5dcdc6dedfef8egf9gfh0ghg1ihi2hji3jik4jkj5lkl6kml7mln8mnm9ono -->
     1205    <html xmlns="http://www.w3.org/1999/xhtml" <?php if ( function_exists( 'language_attributes' ) ) language_attributes(); ?>>
     1206    <head>
     1207        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     1208        <title><?php echo $title ?></title>
     1209        <link rel="stylesheet" href="<?php echo $admin_dir; ?>css/install.css" type="text/css" />
     1210    <?php
     1211    if ( 'rtl' == $text_direction ) : ?>
     1212        <link rel="stylesheet" href="<?php echo $admin_dir; ?>css/install-rtl.css" type="text/css" />
     1213    <?php endif; ?>
     1214    </head>
     1215    <body id="error-page">
     1216    <?php endif; ?>
     1217        <?php echo $message; ?>
     1218    </body>
     1219    </html>
     1220    <?php
     1221        die();
     1222    }
     1223
     1224    //** end soma_wp_die_handler
     1225
     1226    // Remove 'Administrator' from the list of roles if the current user is not an admin
     1227    function editable_roles( $roles ){
     1228        if( isset( $roles['administrator'] ) && !current_user_can('administrator') ){
     1229            unset( $roles['administrator']);
     1230        }
     1231        return $roles;
     1232    }
     1233
     1234    // If someone is trying to edit or delete an admin and that user isn't an admin, don't allow it -- IS THIS STILL NEEDED?
     1235    function admin_map_meta_cap( $caps, $cap, $user_id, $args ){
     1236
     1237        switch( $cap ){
     1238            case 'edit_user':
     1239            case 'remove_user':
     1240            case 'promote_user':
     1241                if( isset($args[0]) && $args[0] == $user_id )
     1242                    break;
     1243                elseif( !isset($args[0]) )
     1244                    $caps[]  = 'do_not_allow';
     1245                $other    = new WP_User( absint($args[0]) );
     1246                if( $other->has_cap( 'administrator' ) ){
     1247                    if(!current_user_can('administrator')){
     1248                        $caps[] = 'do_not_allow';
     1249                    }
     1250                }
     1251            break;
     1252            case 'delete_user':
     1253            case 'delete_users':
     1254                if( !isset($args[0]) )
     1255                    break;
     1256                $other    = new WP_User( absint($args[0]) );
     1257                if( $other->has_cap( 'administrator' ) ){
     1258                    if(!current_user_can('administrator')){
     1259                        $caps[] = 'do_not_allow';
     1260                    }
     1261                }
     1262            break;
     1263            default:
     1264            break;
     1265        }
     1266        return $caps;
     1267    }
     1268
     1269    // AJAX delete images on the fly.
     1270    // can't use nonce checking because the elements calling this function are dynamically created in javascript
     1271    function ajax_unlink_file() {
     1272        $file = somaFunctions::fetch_index($_POST, 'data');     // retrieve file from ajax post
     1273        if ($file && file_exists($file)) {                      // make sure it's there
     1274            $delete = unlink($file);                            // kill it
     1275            if ($delete) {
     1276                $response = array( 'success' => true, 'msg' => 'Successfully deleted the uploaded file!' );
     1277            } else {
     1278                $response = array( 'error' => true, 'msg' => 'Could not delete the uploaded file...' );
     1279            }
     1280        } else {
     1281            $response = array( 'error' => true, 'msg' => 'Could not find the file to delete...' );
     1282        }
     1283        echo json_encode( $response );
     1284        exit;
     1285    }
     1286
     1287    // AJAX delete attachments
     1288    function ajax_delete_attachment() {
     1289        $nonce = check_ajax_referer( 'soma-delete-attachment', 'nonce', false );
     1290        if (!$nonce) {
     1291            $response = array( 'error' => true, 'msg' => 'nonce verification failed...' );
     1292            echo json_encode( $response );
     1293            exit;
     1294        }
     1295        $att_id = somaFunctions::fetch_index($_POST, 'data');
     1296        if ($att_id) {
     1297            $result = wp_delete_attachment($att_id, true);
     1298            $response = array( 'success' => true, 'msg' => $result );
     1299        } else {
     1300            $response = array( 'error' => true, 'msg' => 'Could not find the attachment...' );
     1301        }
     1302        echo json_encode( $response );
     1303        exit;
     1304    }
     1305
     1306
     1307
     1308    // grabs metadata from public vimeo API, returns array
     1309    function fetch_vimeo_meta( $link = null ) {
     1310        if ( !$link ) return new WP_Error('missing', "Must specify a video ID");
     1311        // use the oembed API to parse the raw URL for us
     1312        $oembed = json_decode(file_get_contents("http://vimeo.com/api/oembed.json?url=".urlencode($link)), true);
     1313        $meta = array_shift(unserialize(file_get_contents("http://vimeo.com/api/v2/video/{$oembed['video_id']}.php")));
     1314        $meta = array_merge($oembed, $meta);
     1315        return $meta;
     1316    }
     1317
     1318    // grabs metadata from public youtube API, returns array
     1319    function fetch_youtube_meta( $url = null ) {
     1320        if ( !$url ) return new WP_Error('missing', "Must specify a video ID");
     1321        // extract ID
     1322        if (preg_match('%(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) {
     1323            $id = $match[1];
     1324        }
     1325        $meta = json_decode(file_get_contents("http://gdata.youtube.com/feeds/api/videos/$id?v=2&alt=json"), true);     // json_decode true returns data as array instead of object
     1326        $meta = $meta['entry'];     // isolate the single entry we requested
     1327        return $meta;
     1328    }
     1329
     1330    // grab metadata from external site URL
     1331    // only supporting 3 media sites right now: youtube, vimeo, soundcloud
     1332    function fetch_external_media($url = null, $width = null, $height = null) {
     1333        if ( !$url ) return new WP_Error('missing', "Must specify a valid URL from a supported site");
     1334
     1335        // in case user didn't include http
     1336        if (!preg_match("~^(?:f|ht)tps?://~i", $url)) {
     1337            $url = "http://" . $url;
     1338        }
     1339
     1340        // identify which site
     1341        switch (true) {
     1342            case ( strpos( $url, 'youtube.com' ) > 0 || strpos ($url, 'youtu.be' ) > 0 ) :
     1343                $site = 'youtube';
     1344            break;
     1345            case ( strpos( $url, 'vimeo.com' ) > 0 ) :
     1346                $site = 'vimeo';
     1347            break;
     1348            case ( strpos( $url, 'soundcloud.com' ) > 0 ) :
     1349                $site = 'soundcloud';
     1350            break;
     1351            default:
     1352                return new WP_Error('missing', "Problem with the URL. Can't figure out which site it's from (or it's not supported). Check to make sure you entered it correctly...");
     1353            break;
     1354        }
     1355
     1356
     1357        $media = array();   // init container
     1358        $width = $width ? $width : "853";           // iframe default
     1359        $height = $height ? $height : "480";        // iframe default
     1360
     1361        // extract ID, fetch meta, build output
     1362        switch ($site) {
     1363            case "youtube";
     1364                if (preg_match('%(?:youtube\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) {
     1365                    $id = $match[1];
     1366                    ob_start();
     1367                        ini_set('user_agent','Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17');
     1368                        // $meta = file_get_contents("http://gdata.youtube.com/feeds/api/videos/".$id."?v=2&alt=json");  // old, don't work??
     1369                        $meta = file_get_contents("https://www.youtube.com/oembed?url=".$url."&format=json");
     1370                        $meta = json_decode($meta, true);   // json_decode true returns data as array instead of object
     1371                    ob_end_clean();
     1372                   
     1373                    $media['url']       = "http://www.youtube.com/watch?v=$id";                                                                 // rebuild external url
     1374                    $media['id']        = $id;
     1375                    $media['site']      = $site;
     1376                    $media['thumb']     = $meta['thumbnail_url'];                                               // grabs url of medium 360x480 image
     1377                    $media['title']     = $meta['title'];
     1378                    // $media['desc']       = stripslashes($meta['media$group']['media$description']['$t']);
     1379                    // $media['duration']   = $meta['media$group']['media$content'][0]['duration'];
     1380                    $media['mobile']    = "youtube:$id";                                                                                    // launches youtube app, i think...
     1381                    $media['embed']     = "<iframe width=\"$width\" height=\"$height\" src=\"http://www.youtube.com/embed/$id?rel=0&hd=1\" frameborder=\"0\" allowfullscreen></iframe>";
     1382                    $media['iframe']    = "http://www.youtube.com/embed/$id?rel=0&hd=1&autoplay=1";                                         // url to pass to iframe renderers
     1383                    // $media['api']        = $meta;                                                                                            // we include everything we got from the site API, just in case (each site formats differently)
     1384                } else {
     1385                    return new WP_Error('missing', "Can't extract ID from youtube URL...");
     1386                }
     1387            break;
     1388            case "vimeo";
     1389                // try to get around 403 forbidden errors when using file_get_contents
     1390                ob_start();
     1391                    ini_set('user_agent','Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.56 Safari/537.17');
     1392                    $oembed = file_get_contents("https://vimeo.com/api/oembed.json?url=".urlencode($url));
     1393                    $oembed = json_decode($oembed, true);
     1394                ob_end_clean();
     1395                if (!empty($oembed)) {              // get oembed metadata
     1396                    $id = $oembed['video_id'];                                                                                              // extract id from api response
     1397                    ob_start();
     1398                        ini_set('user_agent','Mozilla/4.0 (compatible; MSIE 6.0)');
     1399                        $meta = file_get_contents("https://vimeo.com/api/v2/video/$id.php");
     1400                    ob_end_clean();
     1401                    $meta = array_shift(unserialize($meta));                            // get more metadata (note: we're making two distinct calls to API for vimeo)
     1402                    $meta = array_merge($oembed, $meta);                                                                                    // combine the two (including some overlapping keys)
     1403                    $media['url']       = "http://vimeo.com/$id";                                                                           // rebuild external url
     1404                    $media['id']        = $id;                                                                                              // store raw media ID
     1405                    $media['site']      = $site;                                                                                            //
     1406                    $media['thumb']     = $meta['thumbnail_url'];                                                                           // grabs url of default image 640x360
     1407                    $media['title']     = stripslashes($meta['title']);                                                                                 //
     1408                    $media['desc']      = stripslashes($meta['description']);                                                                               //
     1409                    $media['duration']  = $meta['duration'];                                                                                // in seconds
     1410                    $media['mobile']    = $meta['mobile_url'];                                                                              // mobile-friendly display url
     1411                    $media['embed']     = $meta['html'];                                                                                    // auto embed code (should be HTML5/ipad compatible)
     1412                    $media['iframe']    = "http://player.vimeo.com/video/$id?title=0&amp;byline=0&amp;portrait=0&amp;&amp;frameborder=0&amp;autoplay=1";            // url to pass to iframe renderers (like colorbox)
     1413                    $media['api']       = $meta;                                                                                            // we include everything we got from the site API, just in case (each site formats differently)
     1414                } else {
     1415                    return new WP_Error('missing', "Can't extract ID from vimeo URL...");
     1416                }
     1417            break;
     1418            case "soundcloud";
     1419                $clientid = "006b3d9b3bbd5bd6cc7d27ab05d9a11b";     // my soundcloud api id
     1420                if ($meta = json_decode(file_get_contents("http://api.soundcloud.com/resolve?client_id=".$clientid."&format=json&url=".urlencode($url)), true)) {
     1421                //  $oembed = json_decode(file_get_contents("http://soundcloud.com/oembed?format=json&show_comments=false&auto_play=true&url=".urlencode($url)), true);
     1422                    $media['id']        = $meta['id'];
     1423                    $media['url']       = $meta['permalink_url'];
     1424                    $media['site']      = $site;
     1425                    if (empty($meta['artwork_url'])) {
     1426                        $media['thumb'] = $meta['waveform_url'];
     1427                    } else {
     1428                        $media['thumb'] = $meta['artwork_url'];
     1429                    }
     1430                    $media['title']     = $meta['title'];
     1431                    $media['desc']      = $meta['description'];
     1432                    $media['duration']  = $meta['duration'];
     1433                    $media['mobile']    = $meta['stream_url']."?client_id=006b3d9b3bbd5bd6cc7d27ab05d9a11b";
     1434                    $media['direct']    = $meta['stream_url']."?client_id=006b3d9b3bbd5bd6cc7d27ab05d9a11b";
     1435                    $media['iframe']    = "http://w.soundcloud.com/player/?url=".$meta['uri']."?auto_play=true&show_artwork=false&show_comments=false&color=000000&show_playcount=false&sharing=false&show_user=false&liking=false";                // http://developers.soundcloud.com/docs/oembed   --  https://github.com/soundcloud/Widget-JS-API/wiki/widget-options
     1436                    $media['embed']     = '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="'.$media['iframe'].'"></iframe>';
     1437                    $media['format']    = $meta['original_format'];
     1438                    $media['api']       = $meta;                                                    // mp3 in this case
     1439                } else {
     1440                    return new WP_Error('missing', "Can't extract ID from soundcloud URL...");
     1441                }
     1442            break;
     1443        }
     1444
     1445        if (empty($media)) return new WP_Error('missing', "Somehow we failed...");
     1446        // success
     1447        return $media;
     1448    }
     1449
     1450
     1451    // retrieves ID of attachment from given URL
     1452    // source: Rarst  http://wordpress.stackexchange.com/questions/6645/turn-a-url-into-an-attachment-post-id
     1453    function get_attachment_id_from_url( $url ) {
     1454
     1455        $dir = wp_upload_dir();
     1456        $dir = trailingslashit($dir['baseurl']);
     1457
     1458        if( false === strpos( $url, $dir ) )
     1459            return new WP_Error('missing', "Something wrong with this url...");
     1460
     1461        $file = basename($url);
     1462
     1463        $query = array(
     1464            'post_type' => 'attachment',
     1465            'fields' => 'ids',
     1466            'meta_query' => array(
     1467                array(
     1468                    'value' => $file,
     1469                    'compare' => 'LIKE',
     1470                    )
     1471            )
     1472        );
     1473
     1474        $query['meta_query'][0]['key'] = '_wp_attached_file';
     1475        $ids = get_posts( $query );
     1476
     1477        foreach( $ids as $id )
     1478            if ( $url == array_shift( wp_get_attachment_image_src($id, 'full') ) )
     1479                return $id;
     1480
     1481        $query['meta_query'][0]['key'] = '_wp_attachment_metadata';
     1482        $ids = get_posts( $query );
     1483
     1484        foreach( $ids as $id ) {
     1485
     1486            $meta = wp_get_attachment_metadata($id);
     1487
     1488            foreach( $meta['sizes'] as $size => $values )
     1489            if( $values['file'] == $file && $url == array_shift( wp_get_attachment_image_src($id, $size) ) ) {
     1490
     1491                return $id;
     1492            }
     1493        }
     1494
     1495        return new WP_Error('missing', "Somehow we failed...");
     1496    }
     1497
     1498    /**
     1499    * Download an image from the specified URL and attach it to a post.
     1500    * Modified version of core function media_sideload_image() in /wp-admin/includes/media.php  (which returns an html img tag instead of attachment ID)
     1501    * Additional functionality: ability override actual filename, set as post thumbnail, and to pass $post_data to override values in wp_insert_attachment (original only allowed $desc)
     1502    *
     1503    * @since 1.4
     1504    *
     1505    * @param string $url (required) The URL of the image to download
     1506    * @param int $post_id (required) The post ID the media is to be associated with
     1507    * @param bool $thumb (optional) Whether to make this attachment the Featured Image for the post
     1508    * @param string $filename (optional) Replacement filename for the URL filename (do not include extension)
     1509    * @param array $post_data (optional) Array of key => values for wp_posts table (ex: 'post_title' => 'foobar', 'post_status' => 'draft')
     1510    * @return int|object The ID of the attachment or a WP_Error on failure
     1511    */
     1512    function attach_external_image( $url = null, $post_id = null, $thumb = null, $filename = null, $post_data = array() ) {
     1513        if ( !$url || !$post_id ) return new WP_Error('missing', "Need a valid URL and post ID...");
     1514        if ( !self::array_is_associative( $post_data ) ) return new WP_Error('missing', "Must pass post data as associative array...");
     1515
     1516        // Download file to temp location, returns full server path to temp file, ex; /home/somatics/public_html/mysite/wp-content/26192277_640.tmp MUST BE FOLLOWED WITH AN UNLINK AT SOME POINT
     1517        $tmp = download_url( $url );
     1518
     1519        // If error storing temporarily, unlink
     1520        if ( is_wp_error( $tmp ) ) {
     1521            @unlink($file_array['tmp_name']);   // clean up
     1522            $file_array['tmp_name'] = '';
     1523            return $tmp; // output wp_error
     1524        }
     1525
     1526        preg_match('/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $url, $matches);    // fix file filename for query strings
     1527        $url_filename = basename($matches[0]);                                                  // extract filename from url for title
     1528        $url_type = wp_check_filetype($url_filename);                                           // determine file type (ext and mime/type)
     1529
     1530        // override filename if given, reconstruct server path
     1531        if ( !empty( $filename ) ) {
     1532            $filename = sanitize_file_name($filename);
     1533            $tmppath = pathinfo( $tmp );                                                        // extract path parts
     1534            $new = $tmppath['dirname'] . "/". $filename . "." . $tmppath['extension'];          // build new path
     1535            rename($tmp, $new);                                                                 // renames temp file on server
     1536            $tmp = $new;                                                                        // push new filename (in path) to be used in file array later
     1537        }
     1538
     1539        // assemble file data (should be built like $_FILES since wp_handle_sideload() will be using)
     1540        $file_array['tmp_name'] = $tmp;                                                         // full server path to temp file
     1541
     1542        if ( !empty( $filename ) ) {
     1543            $file_array['name'] = $filename . "." . $url_type['ext'];                           // user given filename for title, add original URL extension
     1544        } else {
     1545            $file_array['name'] = $url_filename;                                                // just use original URL filename
     1546        }
     1547
     1548        // set additional wp_posts columns
     1549        if ( empty( $post_data['post_title'] ) ) {
     1550            $post_data['post_title'] = basename($url_filename, "." . $url_type['ext']);         // just use the original filename (no extension)
     1551        }
     1552
     1553        // make sure gets tied to parent
     1554        if ( empty( $post_data['post_parent'] ) ) {
     1555            $post_data['post_parent'] = $post_id;
     1556        }
     1557
     1558        // required libraries for media_handle_sideload
     1559        require_once(ABSPATH . 'wp-admin/includes/file.php');
     1560        require_once(ABSPATH . 'wp-admin/includes/media.php');
     1561        require_once(ABSPATH . 'wp-admin/includes/image.php');
     1562
     1563        // do the validation and storage stuff
     1564        $att_id = media_handle_sideload( $file_array, $post_id, null, $post_data );             // $post_data can override the items saved to wp_posts table, like post_mime_type, guid, post_parent, post_title, post_content, post_status
     1565
     1566        // If error storing permanently, unlink
     1567        if ( is_wp_error($att_id) ) {
     1568            @unlink($file_array['tmp_name']);   // clean up
     1569            return $att_id; // output wp_error
     1570        }
     1571
     1572        // set as post thumbnail if desired
     1573        if ($thumb) {
     1574            set_post_thumbnail($post_id, $att_id);
     1575        }
     1576
     1577        return $att_id;
     1578    }
     1579
     1580    /**
     1581    * Gets the excerpt of a specific post ID or object - if one doesn't exist, it will create one dynamically
     1582    * @since 1.7.3
     1583    *
     1584    * @param - $post - object/int/string - the ID or object of the post to get the excerpt of
     1585    * @param - $length - int - the length of the excerpt in words
     1586    * @param - $tags - string - the allowed HTML tags. These will not be stripped out
     1587    * @param - $extra - string - text to append to the end of the excerpt
     1588    */
     1589    function fetch_excerpt($post, $length = 30, $tags = '<a><em><strong>', $extra = '…') {
     1590
     1591        if (is_wp_error($post)) return $post;
     1592        if (empty($post)) return new WP_Error('missing', "must pass a post argument!");
     1593        if (!is_object($post)) {
     1594            $post = get_post(intval($post));
     1595        }
     1596        if (!is_object($post)) {
     1597            return new WP_Error('error', "Can't retrieve the requested post...");
     1598        }
     1599
     1600        if (has_excerpt($post->ID)) {
     1601            $the_excerpt = $post->post_excerpt;
     1602            return apply_filters('the_content', $the_excerpt);
     1603        } else {
     1604            $the_excerpt = $post->post_content;
     1605        }
     1606
     1607        $the_excerpt = strip_shortcodes(strip_tags($the_excerpt), $tags);
     1608        $the_excerpt = preg_split('/\b/', $the_excerpt, $length * 2+1);
     1609        $excerpt_waste = array_pop($the_excerpt);
     1610        $the_excerpt = implode($the_excerpt);
     1611        $the_excerpt .= $extra;
     1612
     1613        return apply_filters('the_content', $the_excerpt);
     1614    }
     1615
     1616    /**
     1617    * generates HTML link code in conjunction with the soma_go_redirect option
     1618    * won't work unless you've already hooked the soma_go_redirect_codes filter and added a slug/url pair
     1619    * @since 1.7.6
     1620    *
     1621    * @param - $slug - string - properly formatted slug for the url: /go/[slug]
     1622    * @param - $text - string - text to be wrapped inside the <a> tags
     1623    * @return - string - HTML link code
     1624    */
     1625    function make_go_link($slug = null, $text = null) {
     1626        if (empty($slug) || empty($text)) return new WP_Error('missing', "must pass a slug and text argument!");
     1627        $slug = sanitize_title_with_dashes($slug);
     1628        $text = sanitize_title($text);
     1629        $codes = apply_filters('soma_go_redirect_codes', $codes);
     1630        if (array_key_exists($slug, $codes)) {
     1631            return "<a href='{$codes[$slug]}' rel='nofollow'>$text</a>";
     1632        } else {
     1633            return new WP_Error('error', "that slug hasn't been added yet, so I can't give you a URL ...");
     1634        }
     1635
     1636    }
     1637
     1638    // Returns array of details about a file (only works on attachments)
     1639    // -------------------------------------------------------------
     1640    function fetch_file($pid = null) {
     1641        if ( empty( $pid ) ) return false;
     1642        $type = get_post_type( intval( $pid ) );
     1643        if ( $type != "attachment" ) return false;
     1644
     1645        $file_path = get_attached_file( $pid );
     1646        $file = pathinfo( $file_path );                                             // returns array of dirname, basename, extension, filename
     1647        $file['mime'] = get_post_mime_type( $pid );                                 // returns mime type
     1648        $file['url'] = wp_get_attachment_url( $pid );                               // returns full URL for downloading
     1649        $file['secure'] = self::build_request_link('legacy-download', $pid);        // returns secure download URL
     1650        if ( is_file( SOMA_DIR . "images/file-icons/" . $file['extension'] . ".png" ) ) {
     1651            $file['icon'] = SOMA_IMG . 'file-icons/'. $file['extension'].'.png';
     1652        } else {
     1653            $file['icon'] = SOMA_IMG . 'file-icons/bin.png';
     1654        }
     1655        $file['time'] = get_post_time("U", true, $pid);
     1656        return $file;
     1657    }
     1658
     1659    // Return all sub pages of a given post
     1660    // -------------------------------------------------------------
     1661    function fetch_sub_pages($pid = null) {
     1662        if (empty($pid)) return null;
     1663        $post_type = get_post_type($pid);
     1664        $args = array(
     1665            'post_type' => $post_type,
     1666            'child_of' => $pid,
     1667            'parent' => $pid,
     1668            'sort_order' => 'ASC',
     1669            'sort_column' => 'menu_order',
     1670            'numberposts' => -1
     1671        );
     1672        return get_pages($args);
     1673    }
     1674
     1675    // Return all root (top-level) posts of any given post type
     1676    // -------------------------------------------------------------
     1677    function fetch_root_pages($post_type = 'page') {
     1678        $args = array(
     1679            'post_type' => $post_type,
     1680            'parent' => 0,
     1681            'order' => 'ASC',
     1682            'orderby' => 'menu_order',
     1683            'numberposts' => -1
     1684        );
     1685        return get_pages($args);
     1686    }
     1687
     1688    /**
     1689    * Helps in use of dash and underscore in field ID's of richtext field types (dealing with wp_editor() finicky rules)
     1690    * @since 1.8.1
     1691    *
     1692    * @param - $id - string - user defined ID for this field, anything that's not a lowercase letter or a dash or underscore will get trashed!!
     1693    * @return - $sanID - string - safe ID with obfuscations
     1694    */
     1695
     1696    function sanitize_wpeditor_id($id) {
     1697        $sanID = preg_replace('/_+/', 'uuuuu', $id);            // to preserve some backwards compatibility, we'll make special replacements for dashes and underscores, so they'll get preserved
     1698        $sanID = preg_replace('/-+/', 'ddddd', $sanID);
     1699        $sanID = preg_replace('/[^a-z]+/', '', $sanID);         // final pass to eliminate all non-alpha characters
     1700        return $sanID;
     1701    }
     1702
     1703
     1704    function unsanitize_wpeditor_id($id) {
     1705        $unsanID = preg_replace('/uuuuu+/', '_', $id);          // to preserve some backwards compatibility, we'll make special replacements for dashes and underscores, so they'll get preserved
     1706        $unsanID = preg_replace('/ddddd+/', '-', $unsanID);
     1707        return $unsanID;
     1708    }
     1709
     1710
     1711    /**
     1712    * Constructs URL string to be used to trigger the soma_request_ action hook of the somaRequest Class
     1713    * typically used to pass an array of data to a custom function
     1714    *
     1715    * @since 1.8.5
     1716    *
     1717    * @param - $action - (string) identifier used to assemble the action hook
     1718    * @return - $data - (string/array) - stuff to be sent to the hooked function
     1719    */
     1720
     1721    function build_request_link($action = null, $data = null) {
     1722        if (empty($action) || empty($data)) return new WP_Error('error', 'action or data missing for the request!');
     1723        $safedata = http_build_query(array('data' => $data));           // prepare data contents (even associative or indexed arrays) for inclusion in the URL
     1724        $link = get_option('siteurl') . "?soma_request=$action&$safedata&security=" . wp_create_nonce( "soma-request" );
     1725        return $link;
     1726    }
    17251727
    17261728}
  • somatic-framework/trunk/inc/somaMetaboxes.php

    r1131509 r1962318  
    268268                    $meta = somaFunctions::fetch_post_author($post->ID, 'link');
    269269                } else {
    270                     $meta = $post->$field['id'];
     270                    $meta = $post->{$field['id']};
    271271                }
    272272                // gotta make our own field ID that won't conflict with core ID's, which otherwise might get hijacked (confused for the actual core metabox data) during save
  • somatic-framework/trunk/inc/somaOptions.php

    r1317256 r1962318  
    280280    function add_pages() {
    281281        add_menu_page( 'Somatic Framework Options', 'Somatic', 'update_core', 'somatic-framework-options', null, 'dashicons-admin-generic', '91' );
    282         add_submenu_page( 'somatic-framework-options', 'Framework Options', 'Options', 'update_core', 'somatic-framework-options', create_function( null, 'somaOptions::soma_options_page("options");' ) );
    283         add_submenu_page( 'somatic-framework-options', 'Advanced', 'Advanced', 'update_core', 'somatic-framework-advanced', create_function( null, 'somaOptions::soma_options_page("advanced");' ) );
    284         add_submenu_page( 'somatic-framework-options', 'System Reports', 'Reports', 'update_core', 'somatic-framework-reports', create_function( null, 'somaOptions::soma_options_page("reports");' ) );
     282        add_submenu_page( 'somatic-framework-options', 'Framework Options', 'Options', 'update_core', 'somatic-framework-options',
     283            function() { somaOptions::soma_options_page("options"); }
     284        );
     285        add_submenu_page( 'somatic-framework-options', 'Advanced', 'Advanced', 'update_core', 'somatic-framework-advanced',
     286            function() { somaOptions::soma_options_page("advanced"); }
     287        );
     288        add_submenu_page( 'somatic-framework-options', 'System Reports', 'Reports', 'update_core', 'somatic-framework-reports',
     289            function() { somaOptions::soma_options_page("reports"); }
     290        );
    285291        // add_submenu_page('options-general.php', 'Help Text', 'Help Text', 'update_core', 'soma-help-editor', array(__CLASS__,'soma_help_page'), SOMA_IMG.'soma-downloads-menu.png');
    286292    }
     
    390396    function soma_help_validate( $input ) {
    391397        // strip html
    392         array_walk( $input, create_function( '&$v,$k', '$v = wp_filter_nohtml_kses($v);' ) );
     398        array_walk( $input, function( &$v,$k ) { $v = wp_filter_nohtml_kses($v); } );
    393399        return $input;
    394400    }
  • somatic-framework/trunk/inc/somaSave.php

    r789047 r1962318  
    4444        // metadata fields complete?
    4545        if ( has_term( 'incomplete', 'metadata', $pid ) ) {
    46             add_filter('redirect_post_location', create_function('$location','return add_query_arg("meta_missing", true, $location);'));
     46            add_filter('redirect_post_location', function($location) { return add_query_arg("meta_missing", true, $location); }
     47            );
    4748            $meta_missing = true;
    4849        }
     
    5556                $wpdb->update( $wpdb->posts, array( 'post_status' => 'draft' ), array( 'ID' => $pid ) );
    5657                // filter the query URL to change the published message
    57                 add_filter('redirect_post_location', create_function('$location','return add_query_arg("message", "4", $location);'));
     58                add_filter('redirect_post_location', function($location) { return add_query_arg("message", "4", $location); } );
    5859                // filter the query URL to display failure notice
    59                 add_filter('redirect_post_location', create_function('$location','return add_query_arg("validation", "fail", $location);'));
     60                add_filter('redirect_post_location', function($location) { return add_query_arg("validation", "fail", $location); } );
    6061            } else {
    6162                // filter the query URL to display success notice
    62                 add_filter('redirect_post_location', create_function('$location','return add_query_arg("validation", "submitted", $location);'));
     63                add_filter('redirect_post_location', function($location) { return add_query_arg("validation", "submitted", $location); } );
    6364            }
    6465        }
     
    7172                $wpdb->update( $wpdb->posts, array( 'post_status' => 'pending' ), array( 'ID' => $pid ) );
    7273                // filter the query URL to change the published message
    73                 add_filter('redirect_post_location', create_function('$location','return add_query_arg("message", "4", $location);'));
     74                add_filter('redirect_post_location', function($location) { return add_query_arg("message", "4", $location); } );
    7475                // filter the query URL to display failure notice
    75                 add_filter('redirect_post_location', create_function('$location','return add_query_arg("validation", "fail", $location);'));
     76                add_filter('redirect_post_location', function($location) { return add_query_arg("validation", "fail", $location); } );
    7677            } else {
    7778                if ( isset($_POST['publish']) && $_POST['post_status'] == 'publish' ) {
    7879                    // filter the query URL to display success notice (only if we're publishing, not just updating a published item)
    79                     add_filter('redirect_post_location', create_function('$location','return add_query_arg("validation", "success", $location);'));
     80                    add_filter('redirect_post_location', function($location) { return add_query_arg("validation", "success", $location); } );
    8081                }
    8182            }
     
    223224                    //
    224225                    if ($field['data'] == 'core') {
    225                         $old = $post->$field['id'];
     226                        $old = $post->{$field['id']};
    226227                    }
    227228
  • somatic-framework/trunk/inc/somaTypes.php

    r1597748 r1962318  
    286286            // remove for each post type
    287287            foreach ( $types as $type ) {
    288                 add_action( 'add_meta_boxes', create_function( '', "
    289                     remove_meta_box( \"$box\", \"$type\", \"side\" );
    290                 " ) );
     288                add_action( 'add_meta_boxes', function() { remove_meta_box( $box, $type, side ); } );
    291289            }
    292290        }
  • somatic-framework/trunk/readme.txt

    r1597763 r1962318  
    55Requires at least: 4.0
    66Tested up to: 4.7
    7 Stable tag: 1.8.12
     7Stable tag: 1.8.13
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    3232
    3333== Changelog ==
     34
     35= 1.8.13 =
     36* tweaks for PHP 7.2 compatibility
    3437
    3538= 1.8.12 =
  • somatic-framework/trunk/somaticFramework.php

    r1598280 r1962318  
    44Plugin URI: http://wordpress.org/extend/plugins/somatic-framework/
    55Description: Adds useful classes for getting the most out of Wordpress' advanced CMS features
    6 Version: 1.8.12
     6Version: 1.8.13
    77Author: Israel Curtis
    88Author URI: mailto:[email protected]
Note: See TracChangeset for help on using the changeset viewer.