<?php /*

 Composr
 Copyright (c) ocProducts, 2004-2016

 See text/EN/licence.txt for full licencing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  ocProducts Ltd
 * @package    content_privacy
 */

/**
 * Get the SQL extension clauses for implementing privacy.
 *
 * @param  ID_TEXT $content_type The content type
 * @param  ID_TEXT $table_alias The table alias in the main query
 * @param  ?MEMBER $viewing_member_id Viewing member to check privacy against (null: current member)
 * @param  string $additional_or Additional OR clause for letting the user through
 * @param  ?MEMBER $submitter Member owning the content (null: do dynamically in query via content hook). Usually pass as null
 * @return array A tuple: extra JOIN clause, extra WHERE clause, table clause (rarely used), direct table WHERE clause (rarely used)
 */
function get_privacy_where_clause($content_type, $table_alias, $viewing_member_id = null, $additional_or = '', $submitter = null)
{
    if ($viewing_member_id === null) {
        $viewing_member_id = get_member();
    }

    $table = get_table_prefix() . 'content_privacy priv';

    $pass_thru = array('', '', $table, '1=1');

    if ($submitter == $viewing_member_id) {
        return $pass_thru;
    }

    if (($content_type[0] == '_') && ($content_type != '_photo')) {
        return $pass_thru; // HACKHACK: No privacy individually set on custom content catalogue entries, and don't want to give default restrictions
    }

    require_code('content');
    $cma_ob = get_content_object($content_type);
    if ($cma_ob === null) {
        $cma_info = null;

        if (has_privilege($viewing_member_id, 'view_private_content')) {
            return $pass_thru;
        }

        $join = '';
    } else {
        $cma_info = $cma_ob->info();

        $first_id_field = (is_array($cma_info['id_field']) ? implode(',', $cma_info['id_field']) : $cma_info['id_field']);

        if (!$cma_info['support_privacy']) {
            return $pass_thru;
        }

        $override_page = $cma_info['cms_page'];
        if (has_privilege($viewing_member_id, 'view_private_content', $override_page)) {
            return $pass_thru;
        }

        $join = ' LEFT JOIN ' . $table . ' ON priv.content_id=' . db_cast($table_alias . '.' . $first_id_field, 'CHAR') . ' AND ' . db_string_equal_to('priv.content_type', $content_type);
    }

    $where = ' AND (';

    // Pass: If no privacy row defined at all
    $where .= 'priv.content_id IS NULL';

    // Pass: If guest viewing allowed
    $where .= ' OR priv.guest_view=1';
    if (!is_guest($viewing_member_id)) {
        // Pass: If self
        if ($cma_info !== null) {
            $where .= ' OR ' . $table_alias . '.' . $cma_info['submitter_field'] . '=' . strval($viewing_member_id);
        }

        // Pass: If all member viewing allowed
        $where .= ' OR priv.member_view=1';

        // Pass: If friends
        if (addon_installed('chat')) {
            if (($cma_info !== null) || ($submitter !== null)) {
                $where .= ' OR priv.friend_view=1 AND EXISTS(SELECT * FROM ' . get_table_prefix() . 'chat_friends f WHERE f.member_likes=' . (($submitter === null) ? ($table_alias . '.' . $cma_info['submitter_field']) : strval($submitter)) . ' AND f.member_liked=' . strval($viewing_member_id) . ')';
            }
        }

        // Pass: If on allow-list
        $where .= ' OR EXISTS(SELECT * FROM ' . get_table_prefix() . 'content_privacy__members pm WHERE pm.member_id=' . strval($viewing_member_id) . ' AND pm.content_id=' . (($submitter === null) ? db_cast($table_alias . '.' . $first_id_field, 'CHAR') : strval($submitter)) . ' AND ' . db_string_equal_to('pm.content_type', $content_type) . ')';

        // Pass: If some other condition
        if ($additional_or != '') {
            $where .= ' OR ' . $additional_or;
        }
    }
    $where .= ')';

    $table_where = db_string_equal_to('priv.content_type', $content_type) . $where;

    return array($join, $where, $table, $table_where);
}

/**
 * Check to see if some content may be viewed.
 *
 * @param  ID_TEXT $content_type The content type
 * @param  ID_TEXT $content_id The content ID
 * @param  ?MEMBER $viewing_member_id Viewing member to check privacy against (null: current member)
 * @param  string $additional_or Additional OR clause for letting the user through
 * @param  ?MEMBER $submitter Member owning the content (null: do dynamically in query via content hook). Usually pass as null
 * @return boolean Whether there is access
 */
function has_privacy_access($content_type, $content_id, $viewing_member_id = null, $additional_or = '', $submitter = null)
{
    if ($viewing_member_id === null) {
        $viewing_member_id = get_member();
    }

    list($privacy_join, $privacy_where, $privacy_table, $privacy_table_where) = get_privacy_where_clause($content_type, 'e', $viewing_member_id, $additional_or, $submitter);

    require_code('content');
    $cma_ob = get_content_object($content_type);
    if ($cma_ob === null) { // Without JOIN
        $cma_info = null;

        if (has_privilege($viewing_member_id, 'view_private_content')) {
            return true;
        }

        $query = 'SELECT * FROM ' . $privacy_table . ' WHERE ' . $privacy_table_where . ' AND ' . db_string_equal_to('priv.content_id', $content_id);
    } else { // With JOIN
        $cma_info = $cma_ob->info();

        $first_id_field = (is_array($cma_info['id_field']) ? implode(',', $cma_info['id_field']) : $cma_info['id_field']);

        if (!$cma_info['support_privacy']) {
            return true;
        }

        $override_page = $cma_info['cms_page'];
        if (has_privilege($viewing_member_id, 'view_private_content', $override_page)) {
            return true;
        }

        if ($cma_info['id_field_numeric']) {
            $where = 'e.' . $first_id_field . '=' . strval(intval($content_id));
        } else {
            $where = db_string_equal_to('e.' . $first_id_field, $content_id);
        }
        $query = 'SELECT * FROM ' . get_table_prefix() . $cma_info['table'] . ' e' . $privacy_join . ' WHERE ' . $where . $privacy_where;
    }

    $results = $GLOBALS['SITE_DB']->query($query, 1);

    return array_key_exists(0, $results);
}

/**
 * Check to see if some content may be viewed. Exit with an access denied if not.
 *
 * @param  ID_TEXT $content_type The content type
 * @param  ID_TEXT $content_id The content ID
 * @param  ?MEMBER $viewing_member_id Viewing member to check privacy against (null: current member)
 */
function check_privacy($content_type, $content_id, $viewing_member_id = null)
{
    if (!has_privacy_access($content_type, $content_id, $viewing_member_id)) {
        require_lang('content_privacy');
        access_denied('PRIVACY_BREACH');
    }
}

/**
 * Find list of members who may view some content.
 *
 * @param  ID_TEXT $content_type The content type
 * @param  ID_TEXT $content_id The content ID
 * @param  boolean $strict_all Whether to get a full list including friends even when there are over a thousand friends
 * @return ?array A list of member IDs that have access (null: no restrictions)
 */
function privacy_limits_for($content_type, $content_id, $strict_all = false)
{
    $rows = $GLOBALS['SITE_DB']->query_select('content_privacy', array('*'), array('content_type' => $content_type, 'content_id' => $content_id), '', 1);
    if (array_key_exists(0, $rows)) {
        $row = $rows[0];

        $member_view = $row['member_view'];
        $friend_view = $row['friend_view'];
        $guest_view = $row['guest_view'];
        $additional_access = collapse_1d_complexity('member_id', $GLOBALS['SITE_DB']->query_select('content_privacy__members', array('member_id'), array('content_type' => $content_type, 'content_id' => $content_id)));
    } else {
        // Is something being set right now by POST? (this layers on EXTRA security, so is not any kind of security risk)
        if (cms_srv('REQUEST_METHOD') == 'POST') {
            require_code('content_privacy2');
            list($privacy_level, $_additional_access) = read_privacy_fields();
            if ($privacy_level == '') {
                return null;
            }
            $additional_access = array();
            foreach ($_additional_access as $member) {
                $member_id = $GLOBALS['FORUM_DRIVER']->get_member_from_username($member);
                if ($member_id !== null) {
                    $additional_access[] = $member_id;
                }
            }
            list($member_view, $friend_view, $guest_view) = privacy_level_to_binary_settings($privacy_level);
        } else {
            return null;
        }
    }

    if ($guest_view == 1) {
        return null;
    }
    if ($member_view == 1) {
        return null;
    }

    $members = array();

    require_code('content');
    list(, $content_submitter) = content_get_details($content_type, $content_id);

    $members[] = $content_submitter;

    if (($friend_view == 1) && (addon_installed('chat'))) {
        $cnt = $GLOBALS['SITE_DB']->query_select_value('chat_friends', 'COUNT(*)', array('member_likes' => $content_submitter));
        if (($strict_all) || ($cnt <= 1000/*safety limit*/)) {
            $friends = $GLOBALS['SITE_DB']->query_select('chat_friends', array('member_liked'), array('member_likes' => $content_submitter));
            $members = array_merge($members, collapse_1d_complexity('member_liked', $friends));
        }
    }

    $members = array_merge($members, $additional_access);

    return $members;
}
