<?php
/**
* This file implements REST API class
*
* This file is part of the evoCore framework - {@link http://evocore.net/}
* See also {@link https://github.com/b2evolution/b2evolution}.
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package api
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* REST API
*
* @package api
*/
class RestApi
{
/**
* @var string Module name: 'collections', 'users'
*/
private $module = NULL;
/**
* @var array Arguments that go from request string,
* for example: array( 0 => "collections", 1 => "a", 2 => "posts" )
*/
private $args = array();
/**
* @var array Response, This array collects all data that should be printed
* out on screen in JSON format at the end of execution:
*/
private $response = array();
/**
* Constructor
*
* @param string Request string after "/api/v1/", for example: "collections/a/posts"
*/
public function __construct( $request )
{
// Authenticate a user:
$this->user_authentication();
// Set arguments from request string:
$this->args = explode( '/', $request );
if( isset( $this->args[0] ) )
{ // Initialize module name:
$this->module = $this->args[0];
}
}
/**
* Log in a user
*
* @param string Login
* @param string Password
* @return boolean TRUE on success log in, HALT on error
*/
private function user_log_in( $entered_login, $entered_password )
{
global $current_User, $failed_logins_lockout, $failed_logins_before_lockout, $UserSettings, $Settings, $Session, $localtimenow;
$UserCache = & get_UserCache();
// Note: login and password cannot include ' or " or > or <
$entered_login = utf8_strtolower( utf8_strip_tags( $entered_login ) );
$entered_password = utf8_strip_tags( $entered_password );
if( is_email( $entered_login ) )
{ // We have an email address instead of login name
// Get user by email and password:
list( $User, $exists_more ) = $UserCache->get_by_emailAndPwd( $entered_login, $entered_password );
}
elseif( is_valid_login( $entered_login ) === true )
{ // Make sure that we can load the user:
$User = & $UserCache->get_by_login( $entered_login );
}
if( empty( $User ) )
{ // Wrong authentication because user is not found by requested login/email and password in DB:
if( is_logged_in() )
{ // Logout current user:
logout();
}
$this->halt( T_('Wrong login/password.'), 'wrong_login_pass', '403' );
// Exit here.
}
if( is_logged_in() )
{ // If current user is logged in
if( $User->ID == $current_User->ID )
{ // The requested user is already logged in, Don't relog in it on each request:
return true;
// Exit here.
}
else
{ // Log out current user because we should log in new user:
logout();
}
}
// Check user login attempts:
$login_attempts = $UserSettings->get( 'login_attempts', $User->ID );
$login_attempts = empty( $login_attempts ) ? array() : explode( ';', $login_attempts );
if( $failed_logins_lockout > 0 && count( $login_attempts ) >= $failed_logins_before_lockout - 1 )
{ // User already has a maximum value of the attempts:
$first_attempt = explode( '|', $login_attempts[0] );
if( $localtimenow - $first_attempt[0] < $failed_logins_lockout )
{ // User has used N attempts during X minutes, Display error and Refuse login
$this->halt( sprintf( T_('There have been too many failed login attempts. This account is temporarily locked. Try again in %s minutes.'), ceil( $failed_logins_lockout / 60 ) ), 'login_attempt_failed', 403 );
// Exit here.
}
}
if( ! $User->check_password( $entered_password ) )
{ // The entered password is not right for requested user
// Save new login attempt into DB:
if( count( $login_attempts ) >= $failed_logins_before_lockout - 1 )
{ // Unset first attempt to clear a space for new attempt
unset( $login_attempts[0] );
}
$login_attempts[] = $localtimenow.'|'.( array_key_exists( 'REMOTE_ADDR', $_SERVER ) ? $_SERVER['REMOTE_ADDR'] : '' );
$UserSettings->set( 'login_attempts', implode( ';', $login_attempts ), $User->ID );
$UserSettings->dbupdate();
$this->halt( T_('Wrong login/password.'), 'wrong_login_pass', '403' );
// Exit here.
}
if( $User->check_status( 'is_closed' ) )
{ // User account was closed, don't log in it:
$this->halt( T_('This account is closed. You cannot log in.'), 'closed_account', '403' );
// Exit here.
}
elseif( $Settings->get( 'system_lock' ) && ! $User->check_perm( 'users', 'edit' ) )
{ // System is locked for maintenance and current user has no permission to log in this mode
$this->halt( T_('You cannot log in at this time because the system is under maintenance. Please try again in a few moments.'), 'system_maintenance', '503' );
// Exit here.
}
// All checks are good, so we can log in the requested user:
$current_User = $User;
$Session->set_User( $current_User );
if( ! empty( $login_attempts ) )
{ // Clear the attempts list:
$UserSettings->delete( 'login_attempts', $current_User->ID );
$UserSettings->dbupdate();
}
return true;
}
/**
* Authenticate a user
*/
private function user_authentication()
{
global $current_User;
if( isset( $_SERVER, $_SERVER['PHP_AUTH_USER'] ) &&
( ! is_logged_in() || $current_User->get( 'login' ) != $_SERVER['PHP_AUTH_USER'] ) )
{ // Do basic HTTP authentication when user login is provided AND
// user is not logged in yet OR the provided login is defferent than login of the current User:
$this->user_log_in( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] );
}
}
/**
* Execute request
*
* @param boolean TRUE on success result
*/
public function execute()
{
if( empty( $this->module ) )
{ // Wrong request because module is not defined:
$this->halt( 'Module is not defined' );
// Exit here.
}
if( ! method_exists( $this, 'module_'.$this->module ) )
{ // Unknown module:
$this->halt( 'Unknown module "'.$this->module.'"', 'unknown_module' );
// Exit here.
}
// Call module to prepare current request:
$this->{'module_'.$this->module}();
// Print out response in JSON format:
$this->display_response();
}
/**
* Halt execution
*
* @param string Error text
* @param integer Status code: 'wrong_route', 'unknown_module'
* @param integer Status number: 404, 403
*/
private function halt( $error_text, $status_code = 'wrong_route', $status_number = 404 )
{
// Set status code number:
header_http_response( $status_number );
// Add info about error:
$this->add_response( 'code', $status_code );
$this->add_response( 'message', $error_text );
$this->add_response( 'data', array( 'status' => $status_number ) );
// Print out response in JSON format:
$this->display_response();
// Halt execution here:
exit();
}
/**
* Halt execution with text from $Messages
*
* @param integer Status code: 'wrong_route', 'unknown_module'
* @param integer Status number: 404, 403
* @param string Message types, separated by comma: 'success', 'warning', 'error', 'note'
*/
private function halt_with_Messages( $status_code = 'no_access', $status_number = 403, $message_types = 'error,warning' )
{
global $Messages;
$message_types = explode( ',', $message_types );
$halt_messages = array();
$Messages->close_group(); // Make sure any open message group are closed
foreach( $Messages->messages_text as $m => $message_text )
{
if( in_array( $Messages->messages_type[ $m ], $message_types ) )
{ // Get this message for halt because of message type:
$halt_messages[] = $message_text;
}
}
$halt_messages = empty( $halt_messages ) ? $status_code : implode( ' ', $halt_messages );
// Halt execution:
$this->halt( $halt_messages, $status_code, $status_number );
}
/**
* Add new element in response array
*
* @param string Key or Value ( if second param is NULL ), Use NULL to merge the array from second param with existing response array
* @param mixed Value
* @param string Type of new added item: 'raw', 'integer', 'array'
*/
private function add_response( $key, $value = NULL, $type = 'raw' )
{
if( $value === NULL )
{ // Use auto key:
$this->response[] = $key;
}
elseif( $key === NULL && is_array( $value ) )
{ // Merge new data array to response array:
$this->response = array_merge( $this->response, $value );
}
else
{ // Use defined key:
switch( $type )
{
case 'array':
if( ! isset( $this->response[ $key ] ) || ! is_array( $this->response[ $key ] ) )
{ // Initialize array:
$this->response[ $key ] = array();
}
// Add new item to array:
$this->response[ $key ][] = $value;
break;
case 'integer':
$this->response[ $key ] = intval( $value );
break;
default:
// raw (does nothing)
$this->response[ $key ] = $value;
}
}
}
/**
* Print out the response array in JSON format:
*/
private function display_response()
{
// Send the predefined cookies:
evo_sendcookies();
// Set JSON content type:
headers_content_mightcache( 'application/json' );
// Convert array to JSON format and print out:
echo json_encode( $this->response );
}
/**** MODULE COLLECTIONS ---- START ****/
/**
* Call module to prepare request for collections
*/
private function module_collections()
{
global $Collection, $Blog;
switch( $_SERVER['REQUEST_METHOD'] )
{
case 'GET':
// List of valid resources
$valid_resources = array( '', 'view', 'items', 'posts', 'search', 'assignees', 'linked' );
break;
case 'PUT':
// List of valid resources
$valid_resources = array( 'favorite', 'items_flag' );
break;
default:
$this->halt( 'Request method not supported for the requested resource', 'wrong_request', 405 );
// Exit here
}
if( ! empty( $this->args[1] ) )
{
$coll_controller = ! empty( $this->args[2] ) ? $this->args[2] : 'view';
}
else
{
$coll_controller = '';
}
if( ! empty( $coll_controller ) )
{ // Initialize data for request of the selected collection:
// Collection urlname:
$coll_urlname = $this->args[1];
$BlogCache = & get_BlogCache();
if( ( $Collection = $Blog = $BlogCache->get_by_urlname( $coll_urlname, false ) ) === false )
{ // Collection is not detected in DB by requested url name:
$this->halt( 'No collection found in DB by requested url name "'.$coll_urlname.'"', 'unknown_collection', 404 );
// Exit here.
}
// Check access to the requested collection:
$allow_access = $Blog->get_setting( 'allow_access' );
if( $allow_access != 'public' )
{ // If the collection is not public:
if( ! is_logged_in() )
{ // Only logged in users have an access to the collection:
$this->halt( T_('You need to log in before you can access this section.'), 'access_requires_login', 403 );
// Exit here.
}
elseif( $allow_access == 'members' )
{ // Check if current user is member of the collection:
if( ! check_user_perm( 'blog_ismember', 'view', false, $Blog->ID ) )
{ // Current user cannot access to the collection:
$this->halt( T_('You are not a member of this section, therefore you are not allowed to access it.'), 'access_denied', 403 );
// Exit here.
}
}
}
}
// Try to get an object ID and action for example request "<baseurl>/api/v1/collections/<collname>/items/<id>/action":
$object_ID = empty( $this->args[3] ) ? NULL : intval( $this->args[3] );
$object_action = empty( $this->args[4] ) ? NULL : $this->args[4];
$coll_controller_params = array();
if( $object_ID && $object_action )
{ // Set controller name for single object and action:
$coll_controller = $coll_controller.'_'.$object_action;
$coll_controller_params[] = $object_ID;
}
if( ! method_exists( $this, 'controller_coll_'.$coll_controller ) )
{ // Unknown controller:
$this->halt( 'Unknown collection controller "'.$coll_controller.'"', 'unknown_controller' );
// Exit here.
}
if( ! in_array( $coll_controller, $valid_resources, true ) )
{ // Invalid request method:
$this->halt( 'Request method not supported for the requested resource', 'wrong_request', 405 );
// Exit here.
}
// Call collection controller to prepare current request:
call_user_func_array( array( $this, 'controller_coll_'.$coll_controller ), $coll_controller_params );
}
/**
* Default controller/handler
* erhsatingin > function name is a bit wonky, perhaps there's a better way to go around this.
*/
private function controller_coll_()
{
global $DB, $current_User;
$api_page = param( 'page', 'integer', 1 );
$api_per_page = param( 'per_page', 'integer', 10 );
$api_q = param( 'q', 'string', '' );
$api_fields = param( 'fields', 'string', 'shortname' ); // 'id', 'shortname'
$api_restrict_to_available_fileroots = param( 'restrict_to_available_fileroots', 'integer', 0 ); // 1 - Load only collections with available file roots for current user
$api_list_in_frontoffice = param( 'list_in_frontoffice', 'string', 'public' ); // 'public' - Load only collections which can be viewed for current user
$BlogCache = & get_BlogCache();
if( $api_list_in_frontoffice == 'public' )
{ // SQL to get ONLY public collections:
$SQL = $BlogCache->get_public_colls_SQL();
$count_SQL = $BlogCache->get_public_colls_SQL();
$count_SQL->SELECT( 'COUNT( blog_ID )' );
}
else
{ // SQL to get ALL collections that can be seen by currently logged in User:
$SQL = $BlogCache->get_available_colls_SQL();
$count_SQL = $BlogCache->get_available_colls_SQL();
$count_SQL->SELECT( 'COUNT( blog_ID )' );
}
if( ! empty( $api_q ) )
{ // Search collections by keyword:
$search_sql_where = array();
$search_fields = empty( $api_fields ) ? array( 'shortname' ) : explode( ',', $api_fields );
foreach( $search_fields as $search_field )
{
switch( strtolower( $search_field ) )
{
case 'id':
$search_sql_where[] = 'blog_ID = '.$DB->quote( $api_q );
break;
case 'shortname':
$search_sql_where[] = 'blog_shortname LIKE '.$DB->quote( '%'.$api_q.'%' );
break;
}
}
$search_sql_where = implode( ' OR ', $search_sql_where );
$SQL->WHERE_and( $search_sql_where );
$count_SQL->WHERE_and( $search_sql_where );
}
$collections = array();
if( $api_restrict_to_available_fileroots &&
(
! is_logged_in() ||
! check_user_perm( 'admin', 'restricted' ) ||
! check_user_perm( 'files', 'view' )
) )
{ // Anonymous user has no access to file roots AND also if current use has no access to back-office or to file manager:
$result_count = 0;
}
else
{
if( $api_restrict_to_available_fileroots )
{ // Restrict collections by available file roots for current user:
// SQL analog for check_user_perm( 'blogs', 'view' ) || check_user_perm( 'files', 'edit' ):
$current_User->get_Group();
$check_perm_blogs_view_files_edit_SQL = new SQL();
$check_perm_blogs_view_files_edit_SQL->SELECT( 'grp_ID' );
$check_perm_blogs_view_files_edit_SQL->FROM( 'T_groups' );
$check_perm_blogs_view_files_edit_SQL->FROM_add( 'LEFT JOIN T_groups__groupsettings ON grp_ID = gset_grp_ID AND gset_name = "perm_files"' );
$check_perm_blogs_view_files_edit_SQL->WHERE( 'grp_ID = '.$current_User->Group->ID );
$check_perm_blogs_view_files_edit_SQL->WHERE_and( 'grp_perm_blogs IN ( "viewall", "editall" ) OR gset_value IS NULL OR gset_value IN ( "all", "edit" )' );
$restrict_available_fileroots_sql = '( '.$check_perm_blogs_view_files_edit_SQL->get().' )';
// SQL analog for check_user_perm( 'blog_media_browse', 'view', false, $Blog ):
$check_perm_blog_media_browse_user_SQL = new SQL();
$check_perm_blog_media_browse_user_SQL->SELECT( 'bloguser_blog_ID' );
$check_perm_blog_media_browse_user_SQL->FROM( 'T_coll_user_perms' );
$check_perm_blog_media_browse_user_SQL->WHERE( 'bloguser_user_ID = '.$current_User->ID );
$check_perm_blog_media_browse_user_SQL->WHERE_and( 'bloguser_perm_media_browse <> 0' );
$check_perm_blog_media_browse_group_SQL = new SQL();
$check_perm_blog_media_browse_group_SQL->SELECT( 'bloggroup_blog_ID' );
$check_perm_blog_media_browse_group_SQL->FROM( 'T_coll_group_perms' );
$check_perm_blog_media_browse_group_SQL->WHERE( '( bloggroup_group_ID = '.$current_User->Group->ID.'
OR bloggroup_group_ID IN ( SELECT sug_grp_ID FROM T_users__secondary_user_groups WHERE sug_user_ID = '.$current_User->ID.' ) )' );
$check_perm_blog_media_browse_group_SQL->WHERE_and( 'bloggroup_perm_media_browse <> 0' );
$restrict_available_fileroots_sql .= ' OR blog_owner_user_ID = '.$current_User->ID
.' OR ( blog_advanced_perms <> 0 AND ( '
.' blog_ID IN ( '.$check_perm_blog_media_browse_user_SQL->get().' ) OR '
.' blog_ID IN ( '.$check_perm_blog_media_browse_group_SQL->get().' )'
.' )'
.' )';
$SQL->WHERE_and( $restrict_available_fileroots_sql );
$count_SQL->WHERE_and( $restrict_available_fileroots_sql );
}
$result_count = intval( $DB->get_var( $count_SQL ) );
}
// Prepare pagination:
if( $api_per_page > 0 && $result_count > $api_per_page )
{ // We will have multiple search result pages:
if( $api_page < 1 )
{ // Limit by min page:
$api_page = 1;
}
$total_pages = ceil( $result_count / $api_per_page );
if( $api_page > $total_pages )
{ // Limit by max page:
$api_page = $total_pages;
}
}
else
{ // Only one page of results:
$api_page = 1;
$total_pages = 1;
}
$BlogCache = & get_BlogCache();
$BlogCache->clear();
if( $result_count > 0 )
{ // Select collections only from current page:
if( $api_per_page > 0 )
{ // Limit results by page size only when this is no unlimitted request:
$SQL->LIMIT( ( ( $api_page - 1 ) * $api_per_page ).', '.$api_per_page );
}
$BlogCache->load_by_sql( $SQL );
}
$this->add_response( 'found', $result_count, 'integer' );
$this->add_response( 'page', $api_page, 'integer' );
$this->add_response( 'page_size', $api_per_page, 'integer' );
$this->add_response( 'pages_total', $total_pages, 'integer' );
foreach( $BlogCache->cache as $Blog )
{ // Add each collection row in the response array:
$this->add_response( 'colls', array(
'id' => intval( $Blog->ID ),
'urlname' => $Blog->get( 'urlname' ),
'kind' => $Blog->get( 'type' ),
'shortname' => $Blog->get( 'shortname' ),
'name' => $Blog->get( 'name' ),
'tagline' => $Blog->get( 'tagline' ),
'desc' => $Blog->get( 'longdesc' ),
), 'array' );
}
}
/**
* Call collection controller to prepare request for linked collections
*/
private function controller_coll_linked()
{
global $Collection, $Blog;
// Get linked collections:
$linked_colls = $Blog->get_locales( 'coll' );
// Add current collection on top:
array_unshift( $linked_colls, $Blog->ID );
// Load all collections in cache by single SQL query:
$BlogCache = & get_BlogCache();
$BlogCache->load_list( $linked_colls );
foreach( $linked_colls as $linked_locale => $linked_coll_ID )
{ // Add each collection row in the response array:
if( ! ( $linked_Blog = & $BlogCache->get_by_ID( $linked_coll_ID, false, false ) ) )
{ // Skip wrong linked collection:
continue;
}
$this->add_response( 'colls', array(
'id' => intval( $linked_Blog->ID ),
'urlname' => $linked_Blog->get( 'urlname' ),
'kind' => $linked_Blog->get( 'type' ),
'shortname' => $linked_Blog->get( 'shortname' ),
'name' => $linked_Blog->get( 'name' ).' ('.( $linked_locale === 0 ? $linked_Blog->get( 'locale' ) : $linked_locale ).')',
'tagline' => $linked_Blog->get( 'tagline' ),
'desc' => $linked_Blog->get( 'longdesc' ),
), 'array' );
}
}
/**
* Call collection controller to view collection information
*/
private function controller_coll_view()
{
$coll_urlname = empty( $this->args[1] ) ? 0 : $this->args[1];
$BlogCache = & get_BlogCache();
$Blog = & $BlogCache->get_by_urlname( $coll_urlname, false );
$collection_data = array(
'id' => intval( $Blog->ID ),
'urlname' => $Blog->get( 'urlname' ),
'kind' => $Blog->get( 'type' ),
'shortname' => $Blog->get( 'shortname' ),
'name' => $Blog->get( 'name' ),
'tagline' => $Blog->get( 'tagline' ),
'desc' => $Blog->get( 'longdesc' ) );
$this->add_response( NULL, $collection_data );
}
/**
* Call collection controller to prepare request for items with ANY types
*
* @param array Force filters of request
*/
private function controller_coll_items( $force_filters = array() )
{
global $Collection, $Blog;
// Get param to limit number posts per page:
$api_per_page = param( 'per_page', 'integer', 10 );
// Get param to select current page:
// (NOTE: if this param is not set then param 'paged' is used as filter of ItemList)
$page = param( 'page', 'integer', NULL );
// Get param to know what post fields should be sent in response:
$api_details = param( 'details', 'string', NULL );
// Try to get a post ID for request "<baseurl>/api/v1/collections/<collname>/items/<id>":
$post_ID = empty( $this->args[3] ) ? 0 : $this->args[3];
// Get params for full content:
$content_params = param( 'content_params', 'array', array() );
$content_params = array_merge( array(
'before_content_teaser' => '',
'after_content_teaser' => '',
'before_content_extension' => '',
'after_content_extension' => '',
'image_position_teaser' => 'teaser,teaserperm,teaserlink',
'image_position_aftermore' => 'aftermore',
'before_images' => '',
'after_images' => '',
'before_image' => '<figure class="evo_image_block">',
'before_image_legend' => '<figcaption class="evo_image_legend">',
'after_image_legend' => '</figcaption>',
'after_image' => '</figure>',
'image_class' => '',
'image_size' => 'original',
'image_limit' => 1000,
'image_link_to' => 'original', // Can be 'original', 'single' or empty
'before_gallery' => '<div class="evo_post_gallery">',
'after_gallery' => '</div>',
'gallery_table_start' => '',
'gallery_table_end' => '',
'gallery_row_start' => '',
'gallery_row_end' => '',
'gallery_cell_start' => '<div class="evo_post_gallery__image">',
'gallery_cell_end' => '</div>',
'gallery_image_size' => 'crop-80x80',
'gallery_image_limit' => 1000,
'gallery_image_link_to' => 'original', // Can be 'original', 'single' or empty
'gallery_colls' => 5,
'gallery_order' => '', // Can be 'ASC', 'DESC', 'RAND' or empty
), $content_params );
$ItemList2 = new ItemList2( $Blog, $Blog->get_timestamp_min(), $Blog->get_timestamp_max(), $api_per_page, 'ItemCache', '' );
if( $post_ID )
{ // Get only one requested post:
if( ctype_digit( ( string ) $post_ID ) )
{ // Get by post ID:
$ItemList2->set_filters( array( 'post_ID' => $post_ID ), true, true );
}
else
{ // Get by url slug:
$ItemList2->set_filters( array( 'post_title' => $post_ID ), true, true );
}
}
else
{ // Load all available params from request to filter the posts list:
$ItemList2->load_from_Request( false );
}
if( $ItemList2->filters['types'] == $ItemList2->default_filters['types'] )
{ // Allow all post types by default for this request:
$ItemList2->set_filters( array( 'itemtype_usage' => NULL ), true, true );
}
if( $page !== NULL )
{ // Set page from request:
$ItemList2->set_filters( array( 'page' => $page ), true, true );
}
if( ! empty( $force_filters ) )
{ // Force filters:
$ItemList2->set_filters( $force_filters, true, true );
}
// Run the items list query:
$ItemList2->query();
if( ! $post_ID )
{ // Add data of posts list to response:
$this->add_response( 'found', $ItemList2->total_rows, 'integer' );
$this->add_response( 'page', $ItemList2->page, 'integer' );
$this->add_response( 'page_size', $ItemList2->limit, 'integer' );
$this->add_response( 'pages_total', $ItemList2->total_pages, 'integer' );
}
if( $api_details == '*' || ( $post_ID && empty( $api_details ) ) )
{ // Use all possible fields for single post request or if it is defined to current request:
$api_details = array(
'id',
'datestart',
'urltitle',
'type',
'title',
'content',
'excerpt',
'teaser',
'URL',
'attachments',
);
}
elseif( empty( $api_details ) )
{ // For posts list get only ID and title by default:
$api_details = array( 'id', 'title' );
}
else
{ // Use custom fields from request:
$api_details = explode( ',', $api_details );
}
// Add each post row in the response array:
while( $Item = & $ItemList2->get_next() )
{
// Initialize data for each item:
$item_data = array();
foreach( $api_details as $api_details_field )
{
switch( $api_details_field )
{
case 'id':
$item_data['id'] = intval( $Item->ID );
break;
case 'datestart':
$item_data['datestart'] = $Item->get( 'datestart' );
break;
case 'urltitle':
$item_data['urltitle'] = $Item->get( 'urltitle' );
break;
case 'type':
$item_data['type'] = $Item->get_type_setting( 'name' );
break;
case 'title':
$item_data['title'] = $Item->get( 'title' );
break;
case 'content':
$item_data['content'] =
$Item->get_images( array_merge( $content_params, array(
'before' => $content_params['before_images'],
'after' => $content_params['after_images'],
'limit' => $content_params['image_limit'],
'restrict_to_image_position' => $content_params['image_position_teaser'],
) ) ).
$Item->get_content_teaser( '#', '#', 'htmlbody', array_merge( $content_params, array(
'before' => $content_params['before_content_teaser'],
'after' => $content_params['after_content_teaser'],
) ) ).
$Item->get_images( array_merge( $content_params, array(
'before' => $content_params['before_images'],
'after' => $content_params['after_images'],
'limit' => $content_params['image_limit'],
'restrict_to_image_position' => $content_params['image_position_aftermore'],
) ) ).
$Item->get_content_extension( '#', true, 'htmlbody', array_merge( $content_params, array(
'before' => $content_params['before_content_extension'],
'after' => $content_params['after_content_extension'],
) ) );
break;
case 'excerpt':
$item_data['excerpt'] = $Item->get( 'excerpt' );
break;
case 'teaser':
$item_data['teaser'] = $Item->get_content_teaser();
break;
case 'URL':
$item_data['URL'] = $Item->get_permanent_url( '', '', '&' );
break;
case 'attachments':
// Get all(1000) item attachemnts:
$attachments = array();
$LinkOwner = new LinkItem( $Item );
if( $LinkList = $LinkOwner->get_attachment_LinkList( 1000 ) )
{
while( $Link = & $LinkList->get_next() )
{
if( ! ( $File = & $Link->get_File() ) )
{ // No File object
global $Debuglog;
$Debuglog->add( sprintf( 'Link ID#%d of item #%d does not have a file object!', $Link->ID, $Item->ID ), array( 'error', 'files' ) );
continue;
}
if( ! $File->exists() )
{ // File doesn't exist
global $Debuglog;
$Debuglog->add( sprintf( 'File linked to item #%d does not exist (%s)!', $Item->ID, $File->get_full_path() ), array( 'error', 'files' ) );
continue;
}
$attachments[] = array(
'link_ID' => intval( $Link->ID ),
'file_ID' => intval( $File->ID ),
'type' => strval( $File->is_dir() ? 'dir' : $File->type ),
'position' => $Link->get( 'position' ),
'name' => $File->get_name(),
'url' => $File->get_url(),
'title' => strval( $File->get( 'title' ) ),
'alt' => strval( $File->get( 'alt' ) ),
'desc' => strval( $File->get( 'desc' ) ),
);
}
}
$item_data['attachments'] = $attachments;
break;
}
}
if( $post_ID )
{ // If only one post is requested then response should as one level array with post fields:
$this->add_response( NULL, $item_data );
}
else
{ // Add data of each post in separate array of response:
$this->add_response( 'items', $item_data, 'array' );
}
}
if( empty( $item_data ) )
{ // No posts detected:
if( $post_ID )
{ // Wrong post request:
$this->halt( 'Invalid post ID', 'post_invalid_id', 404 );
// Exit here.
}
else
{ // No posts found:
$this->halt( 'No posts found for requested collection', 'no_posts', 404 );
// Exit here.
}
}
}
/**
* Call collection controller to prepare request for items ONLY with item type "post"
*/
private function controller_coll_posts()
{
$this->controller_coll_items( array(
'itemtype_usage' => 'post', // Keep content post types, Exclude pages, intros, sidebar links and ads
) );
}
/**
* Call collection controller to search the chapters, posts, comments and tags
*/
private function controller_coll_search()
{
global $Collection, $Blog, $Session;
// Get additional params:
$api_page = param( 'page', 'integer', 1 );
$api_per_page = param( 'per_page', 'integer', 10 );
$api_exclude_posts = param( 'exclude_posts', 'string', '' );
// What types search: 'all', 'item', 'comment', 'category', 'tag'
// Use separator comma to use several kinds:
$api_kind = param( 'kind', 'string', 'all' );
// Load the search functions:
load_funcs( 'collections/_search.funcs.php' );
// Set a search string:
$search_keywords = empty( $this->args[3] ) ? '' : urldecode( $this->args[3] );
// Try to load existing search results from Session:
$search_params = $Session->get( 'search_params' );
$search_result = $Session->get( 'search_result' );
$search_result_loaded = false;
if( empty( $search_params )
|| ( $search_params['search_keywords'] != $search_keywords ) // We had saved search results but for a different search string
|| ( $search_params['search_blog'] != $Blog->ID ) // We had saved search results but for a different collection
|| ( isset( $search_params['exclude_posts'] ) && $search_params['exclude_posts'] != $api_exclude_posts ) // We had saved search results but for a different posts excluding
|| ( $search_result === NULL ) )
{ // We need to perform a new search:
$search_params = array(
'search_keywords' => $search_keywords,
'search_blog' => $Blog->ID,
'exclude_posts' => $api_exclude_posts,
);
// Perform new search:
$search_result = perform_scored_search( $search_keywords, $api_kind, $api_exclude_posts );
// Save results into session:
$Session->set( 'search_params', $search_params );
$Session->set( 'search_result', $search_result );
$search_result_loaded = true;
}
$search_result = $Session->get( 'search_result' );
if( empty( $search_result ) )
{ // Nothing found:
$this->add_response( 'found', 0, 'integer' );
$this->add_response( 'page', $api_page, 'integer' );
$this->add_response( 'page_size', $api_per_page, 'integer' );
$this->add_response( 'pages_total', 0, 'integer' );
$this->add_response( 'results', array() );
}
else
{
// Prepare pagination:
$result_count = count( $search_result );
$result_per_page = $api_per_page;
if( $result_count > $result_per_page )
{ // We will have multiple search result pages:
$current_page = $api_page;
if( $current_page < 1 )
{
$current_page = 1;
}
$total_pages = ceil( $result_count / $result_per_page );
if( $api_page > $total_pages )
{
$current_page = $total_pages;
}
}
else
{ // Only one page of results:
$current_page = 1;
$total_pages = 1;
}
// Set current page indexes:
$from = ( ( $current_page -1 ) * $result_per_page );
$to = ( $current_page < $total_pages ) ? ( $from + $result_per_page ) : ( $result_count );
// Init caches
$ItemCache = & get_ItemCache();
$CommentCache = & get_CommentCache();
$ChapterCache = & get_ChapterCache();
if( ! $search_result_loaded )
{ // Search result objects are not loaded into memory yet, load them:
// Group required object ids by type:
$required_ids = array();
for( $index = $from; $index < $to; $index++ )
{
$row = $search_result[ $index ];
if( isset( $required_ids[ $row['type'] ] ) )
{
$required_ids[ $row['type'] ][] = $row['ID'];
}
else
{
$required_ids[ $row['type'] ] = array( $row['ID'] );
}
}
// Load each required object into the corresponding cache:
foreach( $required_ids as $type => $object_ids )
{
switch( $type )
{
case 'item':
$ItemCache->load_list( $object_ids );
break;
case 'comment':
$CommentCache->load_list( $object_ids );
break;
case 'category':
$ChapterCache->load_list( $object_ids );
break;
// TODO: we'll probably load "tag" objects once we support tag-synonyms.
default: // Not handled search result type
break;
}
}
}
$this->add_response( 'found', $result_count, 'integer' );
$this->add_response( 'page', $current_page, 'integer' );
$this->add_response( 'page_size', $result_per_page, 'integer' );
$this->add_response( 'pages_total', $total_pages, 'integer' );
// Get results for current page:
for( $index = $from; $index < $to; $index++ )
{
$row = $search_result[ $index ];
$result_data = array(
'kind' => $row['type'],
'id' => intval( $row['ID'] ),
);
switch( $row['type'] )
{
case 'item':
// Prepare to display an Item:
$Item = $ItemCache->get_by_ID( $row['ID'], false );
if( empty( $Item ) )
{ // This Item was deleted, since the search process was executed
continue 2; // skip from switch and skip to the next item in loop
}
$result_data['title'] = $Item->get_title( array( 'link_type' => 'none' ) );
$result_data['desc'] = $Item->get_excerpt();
$result_data['permalink'] = $Item->get_permanent_url( '', '', '&' );
break;
case 'comment':
// Prepare to display a Comment:
$Comment = $CommentCache->get_by_ID( $row['ID'], false );
if( empty( $Comment ) || ( $Comment->status == 'trash' ) )
{ // This Comment was deleted, since the search process was executed
continue 2; // skip from switch and skip to the next item in loop
}
$comment_Item = & $Comment->get_Item();
$result_data['title'] = $comment_Item->get_title( array( 'link_type' => 'none' ) );
$result_data['desc'] = excerpt( $Comment->content );
$result_data['permalink'] = $Comment->get_permanent_url( '&' );
break;
case 'category':
// Prepare to display a Category:
$Chapter = $ChapterCache->get_by_ID( $row['ID'], false );
if( empty( $Chapter ) )
{ // This Chapter was deleted, since the search process was executed
continue 2; // skip from switch and skip to the next item in loop
}
$result_data['title'] = $Chapter->get_name();
$result_data['desc'] = excerpt( $Chapter->get( 'description' ) );
$result_data['permalink'] = $Chapter->get_permanent_url( NULL, NULL, 1, NULL, '&' );
break;
case 'tag':
// Prepare to display a Tag:
list( $tag_name, $post_count ) = explode( ',', $row['name'] );
$result_data['title'] = $tag_name;
$result_data['desc'] = sprintf( T_('%d posts are tagged with \'%s\''), $post_count, $tag_name );
$result_data['permalink'] = url_add_param( $Blog->gen_blogurl(), 'tag='.$tag_name, '&' );
break;
default:
// Other type of result is not implemented
// TODO: maybe find collections (especially in case of aggregation)? users? files?
continue 2;
}
// Add data of the searched thing to response:
$this->add_response( 'results', $result_data, 'array' );
}
}
}
/**
* Call collection controller to prepare request for possible assignees
*
* Request scheme: "<baseurl>/api/v1/collections/<collname>/assignees?q=<search login string>"
*
* @param integer Item ID
*/
private function controller_coll_assignees()
{
global $Collection, $Blog, $DB;
if( ! check_user_perm( 'blog_can_be_assignee', 'edit', false, $Blog->ID ) )
{ // Check permission: Current user must has a permission to be assignee of the collection:
$this->halt( 'You are not allowed to view assigness of the collection "'.$Blog->get( 'name' ).'".', 'no_access', 403 );
// Exit here.
}
if( ! $Blog->get_setting( 'use_workflow' ) )
{ // If workflow is not enabled for the collection:
$this->halt( 'The collection "'.$Blog->get( 'name' ).'" is not used for workflow.', 'no_access', 403 );
// Exit here.
}
$api_q = trim( urldecode( param( 'q', 'string', '' ) ) );
/**
* sam2kb> The code below decodes percent-encoded unicode string produced by Javascript "escape"
* function in format %uxxxx where xxxx is a Unicode value represented as four hexadecimal digits.
* Example string "MAMA" (cyrillic letters) encoded with "escape": %u041C%u0410%u041C%u0410
* Same word encoded with "encodeURI": %D0%9C%D0%90%D0%9C%D0%90
*
* jQuery hintbox plugin uses "escape" function to encode URIs
*
* More info here: http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations
*/
if( preg_match( '~%u[0-9a-f]{3,4}~i', $api_q ) )
{ // Decode UTF-8 string:
$api_q = preg_replace( '~%u([0-9a-f]{3,4})~i', '&#x\\1;', $api_q );
$api_q = html_entity_decode( $api_q, ENT_COMPAT, 'UTF-8' );
}
if( empty( $api_q ) )
{ // Don't allow empty request:
$this->halt( 'Please enter at least one char to find assignees', 'no_access', 403 );
// Exit here.
}
if( $Blog->get( 'advanced_perms' ) )
{ // Load group and user permissions ONLY if collection advanced permissions are enabled:
// Get users which can be assignees of the collection:
$user_perms_SQL = new SQL();
$user_perms_SQL->SELECT( 'user_login' );
$user_perms_SQL->FROM( 'T_users' );
$user_perms_SQL->FROM_add( 'INNER JOIN T_coll_user_perms ON user_ID = bloguser_user_ID' );
$user_perms_SQL->WHERE( 'bloguser_blog_ID = '.$DB->quote( $Blog->ID ) );
$user_perms_SQL->WHERE_and( 'bloguser_can_be_assignee <> 0' );
$users_sql[] = $user_perms_SQL->get();
// Get users which primary groups can be assignees of the collection:
$group_perms_SQL = new SQL();
$group_perms_SQL->SELECT( 'user_login' );
$group_perms_SQL->FROM( 'T_users' );
$group_perms_SQL->FROM_add( 'INNER JOIN T_coll_group_perms ON user_grp_ID = bloggroup_group_ID' );
$group_perms_SQL->WHERE( 'bloggroup_blog_ID = '.$DB->quote( $Blog->ID ) );
$group_perms_SQL->WHERE_and( 'bloggroup_can_be_assignee <> 0' );
$users_sql[] = $group_perms_SQL->get();
// Get users which secondary groups can be assignees of the collection:
$secondary_group_perms_SQL = new SQL();
$secondary_group_perms_SQL->SELECT( 'user_login' );
$secondary_group_perms_SQL->FROM( 'T_users' );
$secondary_group_perms_SQL->FROM_add( 'INNER JOIN T_users__secondary_user_groups ON sug_user_ID = user_ID' );
$secondary_group_perms_SQL->FROM_add( 'INNER JOIN T_coll_group_perms ON sug_grp_ID = bloggroup_group_ID' );
$secondary_group_perms_SQL->WHERE( 'bloggroup_blog_ID = '.$DB->quote( $Blog->ID ) );
$secondary_group_perms_SQL->WHERE_and( 'bloggroup_can_be_assignee <> 0' );
$users_sql[] = $secondary_group_perms_SQL->get();
}
// Get collection's owner because it can be assignee by default:
$owner_SQL = new SQL();
$owner_SQL->SELECT( 'user_login' );
$owner_SQL->FROM( 'T_users' );
$owner_SQL->FROM_add( 'INNER JOIN T_blogs ON blog_owner_user_ID = user_ID' );
$owner_SQL->WHERE( 'blog_ID = '.$DB->quote( $Blog->ID ) );
$users_sql[] = $owner_SQL->get();
// Get assignees which primary groups have a setting to EDIT ALL collections:
$group_setting_SQL = new SQL();
$group_setting_SQL->SELECT( 'user_login' );
$group_setting_SQL->FROM( 'T_users' );
$group_setting_SQL->FROM_add( 'INNER JOIN T_groups ON user_grp_ID = grp_ID' );
$group_setting_SQL->WHERE( 'grp_perm_blogs = "editall"' );
$users_sql[] = $group_setting_SQL->get();
// Union sql queries to execute one query and save an order as one list:
$users_sql = 'SELECT user_login'
.' FROM ( ( '.implode( ' ) UNION ( ', $users_sql ).' ) ) AS uu'
.' WHERE uu.user_login LIKE "'.$DB->escape( $api_q ).'%"'
.' ORDER BY user_login'
.' LIMIT 10';
$user_logins = $DB->get_col( $users_sql );
// Send users logins array as response:
$this->add_response( 'list', $user_logins );
}
/**
* Call collection controller to toggle favorite status
*
* Request scheme: "<baseurl>/api/v1/collections/<collname>/favorite
*
*/
private function controller_coll_favorite()
{
global $current_User, $Collection, $Blog;
if( ! is_logged_in() )
{ // Check permission: Current user must be logged in
$this->halt( 'You are not allowed to set the collection "'.$Blog->get( 'name' ).'" as a favorite.', 'no_access', 403 );
// Exit here.
}
parse_str( file_get_contents( 'php://input' ), $data );
$setting = param_format( $data['setting'], 'integer' );
$r = $Blog->favorite( $current_User->ID, $setting );
if( is_null( $r ) )
{
$this->add_response( 'status', 'fail', 'string' );
$this->add_response( 'errorMsg', T_('Unable to set collection favorite status') );
}
else
{
$this->add_response( 'status', 'ok', 'string' );
$this->add_response( 'setting', $setting );
}
}
/**
* Call item controller to flag by current user
*
* @param integer Item ID
*/
private function controller_coll_items_flag( $item_ID )
{
global $Collection, $Blog;
$ItemCache = & get_ItemCache();
if( ( $Item = & $ItemCache->get_by_ID( $item_ID, false, false ) ) === false )
{ // Item is not detected in DB by requested ID:
$this->halt( 'No item found in DB by requested ID #'.$item_ID, 'unknown_item', 404 );
// Exit here.
}
if( $Item->get_blog_ID() != $Blog->ID )
{ // Item should be called for current collection:
$this->halt( 'You request item #'.$Item->ID.' from another collection "'.$Blog->get( 'urlname' ).'"', 'wrong_item_coll', 403 );
// Exit here.
}
if( ! $Item->can_flag() )
{ // Don't display the flag button if it is not allowed by some reason:
$this->halt( 'You cannot flag the item #'.$Item->ID, 'cannot_flag_item', 403 );
}
// Flag or unflag item for current user:
$Item->update_flag();
// Return current state of flag:
$this->add_response( 'flag', $Item->get_user_data( 'item_flag' ), 'integer' );
}
/**** MODULE COLLECTIONS ---- END ****/
/**** MODULE USERS ---- START ****/
/**
* Call module to prepare request for users
*/
private function module_users()
{
switch( $_SERVER['REQUEST_METHOD'] )
{
case 'GET':
// Check for valid controllers
$user_controller = '';
$user_ID = NULL;
if( ! empty( $this->args[1] ) )
{
if( ! empty( $this->args[2] ) )
{
$user_controller = $this->args[2];
}
else
{
if( preg_match( '/^\d+$/', $this->args[1] ) )
{ // args[1] should be a positive integer and not just any number:
$user_ID = intval( $this->args[1] );
$user_controller = 'view';
}
else
{
$user_controller = $this->args[1];
}
}
}
else
{ // default handler
$user_controller = '';
}
$valid_resources = array( '', 'view', 'recipients', 'authors', 'autocomplete', 'logins', 'search' );
if( isset( $user_ID ) )
{ // Set controller to view the requested user profile:
$default_controller = 'view';
}
else
{
$default_controller = $user_controller;
}
break;
case 'POST':
// check for valid controllers
$valid_resources = array( 'save' );
$default_controller = 'save';
break;
case 'DELETE':
if( empty( $this->args[1] ) )
{
$this->halt( 'Missing user ID', 'user_missing_id', 400 );
// Exit here
}
$valid_resources = array( 'delete' );
$default_controller = 'delete';
break;
default:
$this->halt( 'Request method not supported for the requested resource', 'wrong_request', 405 );
// Exit here
}
$user_controller = $default_controller;
if( ! method_exists( $this, 'controller_user_'.$user_controller ) )
{ // Unknown controller:
$this->halt( 'Unknown user controller "'.$user_controller.'"', 'unknown_controller' );
// Exit here.
}
if( ! in_array( $user_controller, $valid_resources, true ) )
{ // Invalid request method:
$this->halt( 'Request method not supported for the requested resource', 'wrong_request', 405 );
// Exit here.
}
// Call collection controller to prepare current request:
$this->{'controller_user_'.$user_controller}();
}
/**
* Default controller/handler
*/
private function controller_user_()
{
global $Settings;
$api_restrict_to_available_fileroots = param( 'restrict_to_available_fileroots', 'integer', 0 ); // 1 - Load only users with available file roots for current user
if( $api_restrict_to_available_fileroots )
{ // Check if current user has an access to file roots of other users:
global $current_User;
if( is_logged_in() )
{ // Check perms for logged in user:
if( ! ( check_user_perm( 'users', 'moderate' ) && check_user_perm( 'files', 'all' ) ) )
{ // Current user has an access only to file root of own account:
$user_filters = array( 'userids' => array( $current_User->ID ) );
}
// otherwise current user has an access to file roots of all users
}
else
{ // Anonymous user has no access to file roots:
$user_filters = array( 'userids' => array( -1 ) );
}
}
else
{ // Default restriction:
if( ( $access_error_message = check_access_users_list( 'api' ) ) !== true )
{ // Current user has no access to public list of the users,
// Display error message:
$this->halt( $access_error_message, 'no_access', 403 );
// Exit here.
}
}
// Get param to limit number users per page:
$api_per_page = param( 'per_page', 'integer', 10 );
// Get param to select current page:
// (NOTE: if this param is not set then param 'paged' is used as filter of UserList)
$page = param( 'page', 'integer', NULL );
// Get user list params:
$api_list_params = param( 'list_params', 'array:string', array() );
// Alias for filter param 'keywords':
$api_q = param( 'q', 'string', NULL );
if( $api_q !== NULL )
{
set_param( 'keywords', $api_q );
}
// Create result set:
load_class( 'users/model/_userlist.class.php', 'UserList' );
$UserList = new UserList( 'api_', $api_per_page, '', $api_list_params );
$UserList->load_from_Request();
if( ! empty( $user_filters ) )
{ // Filter list:
$UserList->set_filters( $user_filters );
}
if( $page !== NULL )
{ // Set page from request:
$UserList->page = $page;
}
// Execute query:
$UserList->query();
// Add data of users list to response:
$this->add_response( 'found', $UserList->total_rows, 'integer' );
$this->add_response( 'page', $UserList->page, 'integer' );
$this->add_response( 'page_size', $UserList->limit, 'integer' );
$this->add_response( 'pages_total', $UserList->total_pages, 'integer' );
// Add each user row in the response array:
while( $User = & $UserList->get_next() )
{
// ID:
$user_data = array( 'id' => intval( $User->ID ) );
// Picture:
if( $Settings->get( 'allow_avatars' ) )
{ // Only if it is allowed by general settings:
$user_picture = $User->get_avatar_imgtag( 'crop-top-48x48' );
if( preg_match( '#src="([^"]+)"#', $user_picture, $user_picture ) )
{
$user_data['picture'] = $user_picture[1];
}
}
// Login:
$user_data['login'] = $User->get( 'login' );
// Full name:
$user_data['fullname'] = $User->get( 'fullname' );
// City:
$user_data['city'] = $User->get_city_name();
// Add each user row in the response array:
$this->add_response( 'users', $user_data, 'array' );
}
if( empty( $user_data ) )
{ // No users found:
$this->halt( 'No users found', 'no_users', 404 );
// Exit here.
}
}
/**
* Call user controller to view a profile of the requested user
*/
private function controller_user_view()
{
// Get an user ID for request "GET <baseurl>/api/v1/users/<id>":
$user_ID = intval( empty( $this->args[1] ) ? 0 : $this->args[1] );
if( ( $access_error_message = check_access_user_profile( $user_ID, 'api' ) ) !== true )
{ // Current user has no access to public list of the users,
// Display error message:
$this->halt( $access_error_message, 'no_access', 403 );
// Exit here.
}
$UserCache = & get_UserCache();
$User = & $UserCache->get_by_ID( $user_ID, false, false );
if( ! $User )
{ // Wrong user request:
$this->halt( 'Invalid user ID', 'user_invalid_id', 404 );
// Exit here.
}
// ID:
$user_data = array( 'id' => intval( $User->ID ) );
// Pictures:
// Main Picture:
$user_picture = $User->get_avatar_imgtag( is_logged_in() ? 'crop-top-320x320' : 'crop-top-320x320-blur-8' );
$user_picture = ( preg_match( '#src="([^"]+)"#', $user_picture, $user_picture ) ) ? $user_picture[1] : '';
$user_data['picture'] = $user_picture;
// Other pictures:
$user_data['pictures'] = array();
if( check_user_status( 'can_view_user', $user_ID ) )
{ // Display other pictures, but only for logged in and activated users:
$user_pic_links = $User->get_avatar_Links();
foreach( $user_pic_links as $user_pic_Link )
{
$user_pic_url = $user_pic_Link->get_tag( array(
'before_image' => '',
'before_image_legend' => NULL,
'after_image_legend' => NULL,
'after_image' => '',
'image_size' => 'crop-top-80x80',
) );
if( ( preg_match( '#src="([^"]+)"#', $user_pic_url, $user_pic_url ) ) )
{ // Extract image url:
$user_data['pictures'][] = $user_pic_url[1];
}
}
}
// Login:
$user_data['login'] = $User->get( 'login' );
// First name:
$user_data['firstname'] = $User->get( 'firstname' );
// Last name:
$user_data['lastname'] = $User->get( 'lastname' );
// Gender:
$user_data['gender'] = $User->get( 'gender' );
// Location:
$user_data['location'] = array(
'country' => empty( $User->ctry_ID ) ? NULL : array( 'id' => intval( $User->ctry_ID ), 'name' => $User->get_country_name() ),
'region' => empty( $User->rgn_ID ) ? NULL : array( 'id' => intval( $User->rgn_ID ), 'name' => $User->get_region_name() ),
'subregion' => empty( $User->subrg_ID ) ? NULL : array( 'id' => intval( $User->subrg_ID ), 'name' => $User->get_subregion_name() ),
'city' => empty( $User->city_ID ) ? NULL : array( 'id' => intval( $User->city_ID ), 'name' => $User->get_city_name() )
);
// Organizations:
$user_data['organizations'] = array();
$user_organizations = $User->get_organizations();
foreach( $user_organizations as $org )
{
$user_data['organizations'][] = array(
'name' => $org->name,
'url' => $org->url,
);
}
// User fields:
$user_data['userfields'] = array();
// Load the user fields:
$User->userfields_load();
foreach( $User->userfields as $userfield )
{
if( ! isset( $user_data['userfields'][ $userfield->ufgp_name ] ) )
{ // Initalize an array for each group of the user fields:
$user_data['userfields'][ $userfield->ufgp_name ] = array();
}
$user_data['userfields'][ $userfield->ufgp_name ][] = array(
'code' => $userfield->ufdf_code,
'name' => $userfield->ufdf_name,
'value' => $userfield->uf_varchar
);
}
// Add user data in the response:
$this->add_response( NULL, $user_data );
}
/**
* Call user controller to update the requested user OR create new one
*/
private function controller_user_save()
{
global $current_User;
if( ! is_logged_in() )
{ // Must be logged in
$this->halt( T_( 'You are not logged in.' ), 'no_access', 403 );
// Exit here.
}
// Get an user ID for request "POST <baseurl>/api/v1/users/<id>":
$user_ID = empty( $this->args[1] ) ? 0 : intval( $this->args[1] );
if( $user_ID > 0 )
{ // Initialize User object to update it:
$UserCache = & get_UserCache();
$edited_User = & $UserCache->get_by_ID( $user_ID, false, false );
if( ! $edited_User )
{ // Wrong user request:
$this->halt( 'Invalid user ID', 'user_invalid_id' );
// Exit here.
}
if( ! $current_User->can_moderate_user( $edited_User->ID )
&& $edited_User->ID != $current_User->ID )
{ // Current user has no permission to delate the requested user:
$this->halt( T_('You have no permission to edit other users!'), 'no_access', 403 );
// Exit here.
}
}
else
{ // Initialize User object to create new one:
$edited_User = new User();
}
// Clear all messages in order to keep only from function User->update_from_request() below:
global $Messages;
$Messages->clear();
// Create new user or Update the requested user:
$is_new_user = ( $edited_User->ID == 0 ? true : false );
$result = $edited_User->update_from_request( $is_new_user );
if( $result !== true )
{ // There are errors on update the requested user:
$Messages->close_group(); // Make sure any open message group are closed
$this->halt( $Messages->messages_text[0], 'update_failed', 403 );
// Exit here.
}
else
{ // The requested user has been updated successfully
$Messages->close_group(); // Make sure any open message group are closed
$this->halt( $Messages->messages_text[0], 'update_success', ( $is_new_user ? 201 : 200 ) );
// Exit here.
}
}
/**
* Call user controller to delete the requested user
*/
private function controller_user_delete()
{
global $current_User;
if( ! check_user_perm( 'users', 'edit' ) )
{ // Current user has no permission to delete the requested user:
$this->halt( T_('You have no permission to edit other users!'), 'no_access', 403 );
// Exit here.
}
// Get an user ID for request "DELETE <baseurl>/api/v1/users/<id>":
$user_ID = intval( $this->args[1] );
$UserCache = & get_UserCache();
$User = & $UserCache->get_by_ID( $user_ID, false, false );
if( ! $User )
{ // Wrong user request:
$this->halt( 'Invalid user ID', 'user_invalid_id', 404 );
// Exit here.
}
if( $User->ID == $current_User->ID )
{
$this->halt( T_('You can\'t delete yourself!'), 'no_access', 403 );
// Exit here.
}
if( $User->ID == 1 )
{
$this->halt( T_('You can\'t delete User #1!'), 'no_access', 403 );
// Exit here.
}
// Clear all messages in order to keep only from function User->check_delete() below:
global $Messages;
$Messages->clear();
if( ! $User->check_delete( sprintf( T_('Cannot delete User «%s»'), $User->get( 'login' ) ) ) )
{ // There are restrictions on delete the requested user:
$Messages->close_group(); // Make sure any open message group are closed
$this->halt( strip_tags( $Messages->messages_text[0] ), 'delete_restriction', 403 );
// Exit here.
}
if( $User->dbdelete( $Messages ) !== false )
{ // The requested user has been deleted successfully
$this->halt( sprintf( T_('User «%s» deleted.'), $User->get( 'login' ) ), 'delete_success', 200 );
// Exit here.
}
else
{ // Cannot delete the requested user
$this->halt( sprintf( T_('Cannot delete User «%s»'), $User->get( 'login' ) ), 'delete_failed', 403 );
// Exit here.
}
}
/**
* Call user controller to search recipients
*/
private function controller_user_recipients()
{
global $current_User, $DB;
if( ! check_user_perm( 'perm_messaging', 'reply' ) )
{ // Check permission: User is not allowed to view threads
$this->halt( 'You are not allowed to view recipients.', 'no_access', 403 );
// Exit here.
}
if( check_create_thread_limit( true ) )
{ // User has already reached his limit, don't allow to get a users list:
$this->halt_with_Messages();
}
$api_q = param( 'q', 'string', '' );
// Search users:
$users = $this->func_user_search( $api_q, array(
'sql_where' => 'user_ID != '.$DB->quote( $current_User->ID ),
'sql_mask' => '%$login$%',
) );
foreach( $users as $User )
{
if( ! $User->check_status( 'can_receive_pm' ) )
{ // This user is probably closed so don't show it:
continue;
}
$user_data = array(
'id' => $User->ID,
'login' => $User->get( 'login' ),
'fullname' => $User->get( 'fullname' ),
'avatar' => $User->get_avatar_imgtag( 'crop-top-32x32' ),
);
// Add data of each user in separate array of response:
$this->add_response( 'users', $user_data, 'array' );
}
}
/**
* Call user controller to search for authors
*/
private function controller_user_authors()
{
global $current_User, $DB;
$api_q = param( 'q', 'string', '' );
if( ! is_logged_in() )
{
$this->halt( 'You are not allowed to view users.', 'no_access', 403 );
// Exit here.
}
// Search users:
$users = $this->func_user_search( $api_q, array(
'sql_where' => 'user_ID != '.$DB->quote( $current_User->ID ),
'sql_mask' => '%$login$%',
) );
foreach( $users as $User )
{
$user_data = array(
'id' => $User->ID,
'login' => $User->get( 'login' ),
'fullname' => $User->get( 'fullname' ),
'avatar' => $User->get_avatar_imgtag( 'crop-top-32x32' ),
);
// Add data of each user in separate array of response:
$this->add_response( 'users', $user_data, 'array' );
}
}
/**
* Call user controller to search user for autocomplete JS plugin
*/
private function controller_user_autocomplete()
{
global $DB;
$api_q = param( 'q', 'string', '' );
$api_mentioned = param( 'mentioned', 'array:string' ); // User logins mentioned on the page
$api_blog = param( 'blog', 'integer' );
if( is_valid_login( $api_q ) !== true )
{ // Restrict a wrong request:
$this->halt( 'Wrong request', 'wrong_request', 403 );
// Exit here.
}
// Add backslash for special char of sql operator LIKE:
$api_q = str_replace( '_', '\_', $api_q );
$search_params = array();
$order_priorities = array();
if( ! empty( $api_mentioned ) )
{ // Mentioned logins must be ordered on the top:
$order_priorities[] = 'WHEN user_login IN ( '.$DB->quote( $api_mentioned ).' ) THEN 1';
}
if( ! empty( $api_blog ) )
{ // Collection assignees and members must be ordered with priorities 2 and 3:
$search_params['sql_from_add'] = 'LEFT JOIN T_coll_user_perms ON bloguser_user_ID = user_ID AND bloguser_blog_ID = '.$DB->quote( $api_blog ).'
LEFT JOIN T_coll_group_perms ON bloggroup_group_ID = user_grp_ID AND bloggroup_blog_ID = '.$DB->quote( $api_blog );
$order_priorities[] = 'WHEN bloguser_can_be_assignee = 1 OR bloggroup_can_be_assignee = 1 THEN 2';
$order_priorities[] = 'WHEN bloguser_ismember = 1 OR bloggroup_ismember = 1 THEN 3';
}
if( ! empty( $order_priorities ) )
{ // Order users by custom priority:
$search_params['sql_select'] = '*, CASE '.implode( ' ', $order_priorities ).' ELSE 4 END AS user_order_priority';
$search_params['sql_order_by'] = 'user_order_priority, user_login';
}
// Search users:
$users = $this->func_user_search( $api_q, $search_params );
foreach( $users as $User )
{
$user_data = array(
'login' => $User->get( 'login' ),
);
// Add data of each user in separate array of response:
$this->add_response( 'users', $user_data, 'array' );
}
}
/**
* Call user controller to search user for hintbox and typeahead JS plugins
*/
private function controller_user_logins()
{
$api_q = trim( urldecode( param( 'q', 'string', '' ) ) );
$api_status = param( 'status', 'string', '' );
/**
* sam2kb> The code below decodes percent-encoded unicode string produced by Javascript "escape"
* function in format %uxxxx where xxxx is a Unicode value represented as four hexadecimal digits.
* Example string "MAMA" (cyrillic letters) encoded with "escape": %u041C%u0410%u041C%u0410
* Same word encoded with "encodeURI": %D0%9C%D0%90%D0%9C%D0%90
*
* jQuery hintbox plugin uses "escape" function to encode URIs
*
* More info here: http://en.wikipedia.org/wiki/Percent-encoding#Non-standard_implementations
*/
if( preg_match( '~%u[0-9a-f]{3,4}~i', $api_q ) )
{ // Decode UTF-8 string:
$api_q = preg_replace( '~%u([0-9a-f]{3,4})~i', '&#x\\1;', $api_q );
$api_q = html_entity_decode( $api_q, ENT_COMPAT, 'UTF-8' );
}
if( empty( $api_q ) )
{ // Don't allow empty request:
$this->halt( 'Please enter at least one char to find assignees', 'no_access', 403 );
// Exit here.
}
$func_user_search_params = array( 'sql_limit' => 10 );
if( $api_status == 'all' )
{ // Get users with all statuses:
$func_user_search_params['sql_where'] = '';
}
elseif( ! empty( $api_status ) )
{ // Restrict users with requested statuses:
global $DB;
$func_user_search_params['sql_where'] = 'user_status IN ( '.$DB->quote( explode( ',', $api_status ) ).' )';
}
// Search users:
$users = $this->func_user_search( $api_q, $func_user_search_params );
// Check if current user can see other users with ALL statuses:
$can_view_all_users = check_user_perm( 'users', 'view' );
$user_logins = array();
foreach( $users as $User )
{
if( $can_view_all_users || in_array( $User->get( 'status' ), array( 'activated', 'autoactivated', 'manualactivated' ) ) )
{ // Allow to see this user only if current User has a permission to see users with current status:
$user_logins[] = $User->get( 'login' );
}
}
// Send users logins array as response:
$this->add_response( 'list', $user_logins );
}
/**
* Function to search users by login
*
* @param string Search string
* @return array Users
*/
private function func_user_search( $search_string, $params = array() )
{
global $DB;
$params = array_merge( array(
'sql_select' => '*',
'sql_from_add' => '',
'sql_where' => 'user_status IN ( "activated", "autoactivated", "manualactivated" )',
'sql_mask' => '$login$%',
'sql_limit' => 0,
'sql_order_by' => 'user_login',
), $params );
// Get request params:
$api_page = param( 'page', 'integer', 1 );
$api_per_page = param( 'per_page', 'integer', $params['sql_limit'] );
// Initialize SQL to get users:
$users_SQL = new SQL();
$users_SQL->SELECT( $params['sql_select'] );
$users_SQL->FROM( 'T_users' );
if( ! empty( $params['sql_from_add'] ) )
{ // Additional tables:
$users_SQL->FROM_add( $params['sql_from_add'] );
}
if( ! empty( $search_string ) )
{ // Filter by login:
$users_SQL->WHERE( 'user_login LIKE '.$DB->quote( str_replace( '$login$', $search_string, $params['sql_mask'] ) ) );
}
if( ! empty( $params['sql_where'] ) )
{ // Additional restrict:
$users_SQL->WHERE_and( $params['sql_where'] );
}
$users_SQL->ORDER_BY( $params['sql_order_by'] );
// Get a count of users:
$count_users = $DB->get_var( preg_replace( '/SELECT(.+)FROM/i', 'SELECT COUNT( user_ID ) FROM', $users_SQL->get() ) );
// Set page params:
$count_pages = empty( $api_per_page ) ? 1 : ceil( $count_users / $api_per_page );
if( empty( $api_page ) )
{ // Force wrong page number to first:
$api_page = 1;
}
if( $api_page > $count_pages )
{ // Don't allow page number more than total pages:
$api_page = $count_pages;
}
if( $api_per_page > 0 )
{ // Limit users by current page:
$users_SQL->LIMIT( ( ( $api_page - 1 ) * $api_per_page ).', '.$api_per_page );
}
// Load users in cache by SQL:
$UserCache = & get_UserCache();
$UserCache->clear();
$UserCache->load_by_sql( $users_SQL );
if( empty( $UserCache->cache ) )
{ // No users found:
$this->halt( 'No users found', 'no_users', 200 );
// Exit here.
}
$this->add_response( 'found', $count_users, 'integer' );
$this->add_response( 'page', $api_page, 'integer' );
$this->add_response( 'page_size', $api_per_page, 'integer' );
$this->add_response( 'pages_total', $count_pages, 'integer' );
return $UserCache->cache;
}
/**** MODULE USERS ---- END ****/
/**** MODULE TAGS ---- START ****/
/**
* Call module to prepare request for tags
*/
private function module_tags()
{
global $DB;
$term = param( 's', 'string' );
if( substr( $term, 0, 1 ) == '-' )
{ // Prevent chars '-' in first position:
$term = preg_replace( '/^-+/', '', $term );
}
// Deny to use a comma in tag names:
$term = str_replace( ',', ' ', $term );
$term_is_new_tag = true;
$tags = array();
$tags_SQL = new SQL();
$tags_SQL->SELECT( 'tag_name AS id, tag_name AS name' );
$tags_SQL->FROM( 'T_items__tag' );
/* Yura: Here I added "COLLATE utf8_general_ci" because:
* It allows to match "testA" with "testa", and otherwise "testa" with "testA".
* It also allows to find "ee" when we type in "éè" and otherwise.
*
* Erwin: Changed added collation from "utf8_general_ci" to "utf8mb4_general_ci" to match default DB connection collation
*/
$tags_SQL->WHERE( 'tag_name LIKE '.$DB->quote( '%'.$term.'%' ).' COLLATE utf8mb4_general_ci' );
$tags_SQL->ORDER_BY( 'tag_name' );
$tags = $DB->get_results( $tags_SQL->get(), ARRAY_A );
// Check if current term is not an existing tag:
foreach( $tags as $tag )
{
/* Yura: I have added "utf8_strtolower()" below in condition in order to:
* When we enter new tag 'testA' and the tag 'testa' already exists
* then we suggest only 'testa' instead of 'testA'.
*/
if( utf8_strtolower( $tag['name'] ) == utf8_strtolower( $term ) )
{ // Current term is an existing tag
$term_is_new_tag = false;
}
}
if( $term_is_new_tag && ! empty( $term ) )
{ // Add current term in the beginning of the tags list:
array_unshift( $tags, array( 'id' => $term, 'name' => $term ) );
}
$this->add_response( 'tags', $tags );
}
/**** MODULE TAGS ---- END ****/
/**** MODULE USER TAGS ---- START ****/
/**
* Call module to prepare request for user tags
*/
private function module_usertags()
{
global $DB;
$term = param( 's', 'string' );
if( substr( $term, 0, 1 ) == '-' )
{ // Prevent chars '-' in first position:
$term = preg_replace( '/^-+/', '', $term );
}
// Deny to use a comma in tag names:
$term = str_replace( ',', ' ', $term );
$term_is_new_tag = true;
$tags = array();
$tags_SQL = new SQL();
$tags_SQL->SELECT( 'utag_name AS id, utag_name AS name' );
$tags_SQL->FROM( 'T_users__tag' );
/* Yura: Here I added "COLLATE utf8_general_ci" because:
* It allows to match "testA" with "testa", and otherwise "testa" with "testA".
* It also allows to find "ee" when we type in "éè" and otherwise.
*
* Erwin: Changed added collation from "utf8_general_ci" to "utf8mb4_general_ci" to match default DB connection collation
*/
$tags_SQL->WHERE( 'utag_name LIKE '.$DB->quote( '%'.$term.'%' ).' COLLATE utf8mb4_general_ci' );
$tags_SQL->ORDER_BY( 'utag_name' );
$tags = $DB->get_results( $tags_SQL->get(), ARRAY_A );
// Check if current term is not an existing tag:
foreach( $tags as $tag )
{
/* Yura: I have added "utf8_strtolower()" below in condition in order to:
* When we enter new tag 'testA' and the tag 'testa' already exists
* then we suggest only 'testa' instead of 'testA'.
*/
if( utf8_strtolower( $tag['name'] ) == utf8_strtolower( $term ) )
{ // Current term is an existing tag
$term_is_new_tag = false;
}
}
if( $term_is_new_tag && ! empty( $term ) )
{ // Add current term in the beginning of the tags list:
array_unshift( $tags, array( 'id' => $term, 'name' => $term ) );
}
$this->add_response( 'tags', $tags );
}
/**** MODULE USER TAGS ---- END ****/
/**** MODULE POLLS ---- START ****/
private function module_polls()
{
global $DB, $current_User;
if( is_logged_in() && $current_User )
{
$polls = array();
$perm_poll_view = check_user_perm( 'polls', 'view' );
$polls_SQL = new SQL();
$polls_SQL->SELECT( 'pqst_ID, pqst_owner_user_ID, pqst_question_text' );
$polls_SQL->FROM( 'T_polls__question' );
if( ! $perm_poll_view )
{
$polls_SQL->WHERE( 'pqst_owner_user_ID = '.$DB->quote( $current_User->ID ) );
}
$poll_count_SQL = new SQL();
$poll_count_SQL->SELECT( 'COUNT( pqst_ID )' );
$poll_count_SQL->FROM( 'T_polls__question' );
if( ! $perm_poll_view )
{
$poll_count_SQL->WHERE( 'pqst_owner_user_ID = '.$DB->quote( $current_User->ID ) );
}
$polls = $DB->get_results( $polls_SQL->get(), ARRAY_A );
$this->add_response( 'polls', $polls );
}
else
{
$this->halt( 'You are not allowed to view polls.', 'no_access', 403 );
}
}
/**** MODULE POLLS ---- END ****/
/**** MODULE LINKS ---- START ****/
/**
* Call module to prepare request for links
*/
private function module_links()
{
if( ! empty( $this->args[2] ) )
{ // Get action from request string:
$link_action = $this->args[2];
}
else
{ // Get action from param:
$link_action = param( 'action', 'string', '' );
}
// Set link controller '' by default:
$link_controller = '';
$request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? $_SERVER['REQUEST_METHOD'] : 'GET';
switch( $request_method )
{
case 'DELETE':
// Set controller to unlink/delete the requested link:
$link_controller = 'delete';
break;
case 'GET':
case 'POST':
// Actions to update the links:
switch( $link_action )
{
case 'position':
$link_controller = 'change_position';
break;
case 'move_up':
case 'move_down':
$link_controller = 'change_order';
break;
case 'attach':
$link_controller = 'attach';
break;
case 'refresh':
case 'sort':
$link_controller = 'refresh';
break;
case 'copy':
$link_controller = 'copy';
break;
}
break;
}
if( ! method_exists( $this, 'controller_link_'.$link_controller ) )
{ // Unknown controller:
$this->halt( 'Unknown link controller "'.$link_controller.'"', 'unknown_controller' );
// Exit here.
}
// Call collection controller to prepare current request:
$this->{'controller_link_'.$link_controller}();
}
/**
* Get Link object
*
* @return object Link
*/
private function & get_Link()
{
// Get link ID:
$link_ID = empty( $this->args[1] ) ? 0 : intval( $this->args[1] );
$LinkCache = & get_LinkCache();
$Link = & $LinkCache->get_by_ID( $link_ID, false, false );
return $Link;
}
/**
* Check permission if current user can update the requested link
*/
private function link_check_perm()
{
if( ! ( $Link = & $this->get_Link() ) )
{ // Wrong link request:
$this->halt( 'Invalid link ID', 'link_invalid_id' );
// Exit here.
}
$LinkOwner = & $Link->get_LinkOwner();
if( ! $LinkOwner->check_perm( 'edit', false ) )
{ // Current user has no permission to unlink the requested link:
$this->halt( 'You have no permission to edit the requested link!', 'no_access', 403 );
// Exit here.
}
}
/**
* Call link controller to unlink the requested link or delete file completely
*/
private function controller_link_delete()
{
// Check permission if current user can update the requested link:
$this->link_check_perm();
// Action: 'unlink - just unlink file from the owner, 'delete' - unlink and delete the file from disk and DB completely
// Note: param() currently does not work with DELETE and PUT requests
parse_str( file_get_contents('php://input'), $request_params );
$action = isset( $request_params['action'] ) ? $request_params['action'] : '';
$deleted_Link = & $this->get_Link();
$LinkOwner = & $deleted_Link->get_LinkOwner();
if( $link_File = & $deleted_Link->get_File() )
{
syslog_insert( sprintf( 'File %s was unlinked from %s with ID=%s', '[['.$link_File->get_name().']]', $LinkOwner->type, $LinkOwner->get_ID() ), 'info', 'file', $link_File->ID );
}
if( $action == 'delete' && $deleted_Link->can_be_file_deleted() )
{ // Get a linked file to delete it after unlinking if it is allowed for current user:
$linked_File = & $deleted_Link->get_File();
}
// Unlink File from Item/Comment:
$deleted_link_ID = $deleted_Link->ID;
if( $LinkOwner->remove_link( $deleted_Link ) )
{ // If Link has been removed successfully:
$LinkOwner->after_unlink_action( $deleted_link_ID );
if( $action == 'delete' && ! empty( $linked_File ) )
{ // Delete a linked file from disk and DB completely:
$linked_File->unlink();
}
// The requested link has been deleted successfully:
$this->halt( $LinkOwner->translate( 'Link has been deleted from $xxx$.' ), 'delete_success', 200 );
// Exit here.
}
else
{ // The requested link cannot be deleted:
$this->halt( $LinkOwner->translate( 'Cannot delete Link from $xxx$.' ), 'delete_failed', 403 );
// Exit here.
}
}
/**
* Call link controller to change the position of the requested link
*/
private function controller_link_change_position()
{
global $DB, $Session;
// Check permission if current user can update the requested link:
$this->link_check_perm();
$link_position = $this->args[3];
$edited_Link = & $this->get_Link();
$LinkOwner = & $edited_Link->get_LinkOwner();
// Don't display the inline position reminder again until the user logs out or loses the session cookie
if( $link_position == 'inline' )
{
$Session->set( 'display_inline_reminder', 'false' );
}
// Check permission:
$LinkOwner->check_perm( 'edit', true );
$edited_Link->set( 'position', $link_position ); // This returns false if no change was made
if( $edited_Link->dbupdate() !== false ) // This returns NULL if no change was made
{ // update was successful or no change was made
// Update last touched date of Owners
$LinkOwner->update_last_touched_date();
if( $LinkOwner->type == 'item' &&
( $link_position == 'cover' || $link_position == 'background' ) )
{ // Position "Cover" can be used only by one link
// Replace previous position with "Inline"
$DB->query( 'UPDATE T_links
SET link_position = "aftermore"
WHERE link_ID != '.$DB->quote( $link_ID ).'
AND link_itm_ID = '.$DB->quote( $LinkOwner->Item->ID ).'
AND link_position = "cover"' );
}
$this->halt( 'Link position has been updated.', 'change_position_success', 200 );
// Exit here
}
else
{ // return the current value on failure
$this->halt( 'Failed to change link position.', 'change_position_failed', 403 );
// Exit here
}
}
/**
* Call link controller to change order the requested link
*/
private function controller_link_change_order()
{
global $localtimenow;
// Check permission if current user can update the requested link:
$this->link_check_perm();
// Get action from request string:
$link_action = $this->args[2];
$edited_Link = & $this->get_Link();
$LinkOwner = & $edited_Link->get_LinkOwner();
$ownerLinks = $LinkOwner->get_Links();
// TODO fp> when moving an "after_more" above a "teaser" img, it should change to "teaser" too.
// TODO fp> when moving a "teaser" below an "aftermore" img, it should change to "aftermore" too.
// Switch order with the next/prev one:
if( $link_action == 'move_up' )
{
$switchcond = 'return ($loop_Link->get("order") > $i
&& $loop_Link->get("order") < '.$edited_Link->get( 'order' ).');';
$i = -1;
}
else
{
$switchcond = 'return ($loop_Link->get("order") < $i
&& $loop_Link->get("order") > '.$edited_Link->get( 'order' ).');';
$i = PHP_INT_MAX;
}
foreach( $ownerLinks as $loop_Link )
{ // Find nearest order:
if( $loop_Link == $edited_Link )
continue;
if( eval( $switchcond ) )
{
$i = $loop_Link->get( 'order' );
$switch_Link = $loop_Link;
}
}
if( $i > -1 && $i < PHP_INT_MAX )
{ // Switch the links:
if( $LinkOwner->type == 'item' && ( $localtimenow - strtotime( $LinkOwner->Item->last_touched_ts ) ) > 90 )
{
$LinkOwner->Item->create_revision();
}
// Update last touched date of Owners
// Update to last touched date made earlier to prevent subsequent link dbupdate from creating a new revision
$LinkOwner->update_last_touched_date();
$switch_Link->set( 'order', $edited_Link->get( 'order' ) );
// HACK: go through order=0 to avoid duplicate key conflict:
$edited_Link->set( 'order', 0 );
$edited_Link->dbupdate();
$switch_Link->dbupdate();
$edited_Link->set( 'order', $i );
$edited_Link->dbupdate();
// The requested link order has been changed successfully:
$this->halt( ( $link_action == 'move_up' )
? T_('Link has been moved up.')
: T_('Link has been moved down.'),
'change_order_success', 200 );
// Exit here.
}
else
{ // The requested link order has been changed successfully:
$this->halt( T_('Link order has not been changed.'), 'change_order_success', 403 );
// Exit here.
}
}
/**
* Call link controller to change order the requested link
*/
private function controller_link_attach()
{
global $LinkOwner, $current_File;
$link_type = param( 'type', 'string' );
$link_object_ID = param( 'object_ID', 'string' );
$root = param( 'root', 'string' );
$file_path = param( 'path', 'string' );
$LinkOwner = get_LinkOwner( $link_type, $link_object_ID );
if( ! $LinkOwner->check_perm( 'edit', false ) )
{ // Current user has no permission to unlink the requested link:
$this->halt( 'You have no permission to attach a file!', 'no_access', 403 );
// Exit here.
}
$FileCache = & get_FileCache();
list( $root_type, $root_in_type_ID ) = explode( '_', $root, 2 );
if( ! ( $current_File = $FileCache->get_by_root_and_path( $root_type, $root_in_type_ID, $file_path ) ) )
{ // No file:
$this->halt( T_('Nothing selected.'), 'wrong_file', 403 );
// Exit here.
}
// Link the file to Item/Comment:
$link_ID = $current_File->link_to_Object( $LinkOwner );
$LinkCache = & get_LinkCache();
if( $Link = & $LinkCache->get_by_ID( $link_ID, false, false ) )
{ // Add data of new link to response:
// Use the glyph or font-awesome icons if requested by skin
param( 'b2evo_icons_type', 'string', 'fontawesome-glyphicons' );
global $disable_evo_flush;
$LinkOwner = get_LinkOwner( $link_type, $link_object_ID );
// Initialize admin skin:
global $current_User, $UserSettings, $is_admin_page, $adminskins_path, $AdminUI;
$admin_skin = $UserSettings->get( 'admin_skin', $current_User->ID );
$is_admin_page = true;
require_once $adminskins_path.$admin_skin.'/_adminUI.class.php';
$AdminUI = new AdminUI();
// Disable function evo_flush() to correct handle a content below:
$disable_evo_flush = true;
// Get the refreshed content:
ob_start();
$AdminUI->disp_view( 'links/views/_link_list.view.php' );
$refreshed_content = ob_get_clean();
$mask_row = (object) array(
'link_ID' => $Link->ID,
'file_ID' => $current_File->ID,
'file_type' => $current_File->get_file_type(),
'link_position' => $Link->get( 'position' ),
);
$this->add_response( 'link', array(
'ID' => $Link->ID,
'url' => $current_File->get_view_link(),
'preview' => $Link->get_preview_thumb(),
'actions' => link_actions( $Link->ID, 'last', $link_type ),
'position' => display_link_position( $mask_row ),
) );
$this->add_response( 'list_content', $refreshed_content );
}
// File has been attached successfully:
$this->halt( $LinkOwner->translate( 'Selected files have been linked to xxx.' ), 'attach_success', 200 );
// Exit here.
}
/**
* Call link controller to refresh a list of the links
*/
private function controller_link_refresh()
{
global $LinkOwner, $current_File, $disable_evo_flush;
$link_type = param( 'type', 'string' );
$link_object_ID = param( 'object_ID', 'string' );
$fieldset_prefix = param( 'prefix', 'string', NULL );
$LinkOwner = get_LinkOwner( $link_type, $link_object_ID );
if( ! $LinkOwner->check_perm( 'view', false ) )
{ // Current user has no permission to unlink the requested link:
$this->halt( 'You have no permission to list of the links!', 'no_access', 403 );
// Exit here.
}
if( get_param( 'action' ) == 'sort' )
{ // Sort the links:
$ownerLinks = $LinkOwner->get_Links();
usort( $ownerLinks, 'sort_links_by_filename' );
$max_order = 0;
$link_orders = array();
$link_count = count( $ownerLinks );
foreach( $ownerLinks as $link )
{
if( $link->order > $max_order )
{
$max_order = $link->order;
}
$link_orders[] = $link->order;
}
for( $i = 1; $i <= $link_count; $i++ )
{
$ownerLinks[$i - 1]->set( 'order', $i + $max_order );
$ownerLinks[$i - 1]->dbupdate();
}
for( $i = 1; $i <= $link_count; $i++ )
{
if( $ownerLinks[$i -1]->get( 'order' ) != $i )
{
$ownerLinks[$i - 1]->set( 'order', $i );
$ownerLinks[$i - 1]->dbupdate();
}
}
}
// Initialize admin skin:
global $current_User, $UserSettings, $is_admin_page, $adminskins_path, $AdminUI, $inc_path;
$admin_skin = is_logged_in() ? $UserSettings->get( 'admin_skin', $current_User->ID ) : 'bootstrap';
$is_admin_page = true;
require_once $adminskins_path.$admin_skin.'/_adminUI.class.php';
$AdminUI = new AdminUI();
// Disable function evo_flush() to correct handle a content below:
$disable_evo_flush = true;
// Get the refreshed content:
ob_start();
// We do not use $AdminUI->disp_view() because we need the $fieldset_prefix above:
require $inc_path.'links/views/_link_list.view.php';
$refreshed_content = ob_get_clean();
$this->add_response( 'html', $refreshed_content );
if( get_param( 'action' ) == 'sort' )
{ // The sort has been done successfully:
$this->halt( T_('The attachments have been sorted by file name.'), 'sort_success', 200 );
// Exit here.
}
else
{ // The refresh has been done successfully:
$this->halt( 'A list of the links has been refreshed.', 'refresh_success', 200 );
// Exit here.
}
}
/**
* Call link controller to copy Link from one object to another
*/
private function controller_link_copy()
{
$dest_type = param( 'dest_type', 'string' );
$dest_object_ID = param( 'dest_object_ID', 'string' );
$dest_LinkOwner = get_LinkOwner( $dest_type, $dest_object_ID );
if( ! $dest_LinkOwner->check_perm( 'edit', false ) )
{ // Current user has no permission to copy the requested link:
$this->halt( 'You have no permission to list of the links!', 'no_access', 403 );
// Exit here.
}
$source_type = param( 'source_type', 'string' );
$source_object_ID = param( 'source_object_ID', 'string' );
$source_position = trim( param( 'source_position', 'string' ), ',' );
$source_file_type = param( 'source_file_type', 'string', NULL );
$source_LinkOwner = get_LinkOwner( $source_type, $source_object_ID );
$link_list_params = array(
// Sort the attachments to get firstly "Cover", then "Teaser", and "After more" as last order
'sql_select_add' => ', CASE '
.'WHEN link_position = "cover" THEN "1" '
.'WHEN link_position = "teaser" THEN "2" '
.'WHEN link_position = "aftermore" THEN "3" '
.'WHEN link_position = "inline" THEN "4" '
.'ELSE "99999999"' // Use this line only if you want to put the other position types at the end
.'END AS position_order',
'sql_order_by' => 'position_order, link_order',
);
if( ! $source_LinkOwner || ! ( $source_LinkList = $source_LinkOwner->get_attachment_LinkList( 1000, $source_position, $source_file_type, $link_list_params ) ) )
{ // No requested links, Exit here:
$this->halt( 'No requested links!', 'no_links', 404 );
// Exit here.
}
$dest_position = param( 'dest_position', 'string' );
$dest_last_order = $dest_LinkOwner->get_last_order() + 1;
// Limit files per each position, 0 - for unlimit:
$limit_position = param( 'limit_position', 'integer', 0 );
if( $limit_position )
{
$position_counts = array();
$all_position_counts = 0;
}
// Find all attached files of the destination LinkOwner in order to don't link twice same files:
$dest_files_links = array();
$dest_links = $dest_LinkOwner->get_Links();
if( ! empty( $dest_links ) && is_array( $dest_links ) )
{
foreach( $dest_links as $dest_Link )
{
if( $dest_File = & $dest_Link->get_File() )
{
$dest_files_links[ $dest_File->ID ] = $dest_Link->ID;
}
}
}
while( $source_Link = & $source_LinkList->get_next() )
{ // Copy a Link to new object:
if( $limit_position )
{
if( $source_position == '' && $all_position_counts >= $limit_position )
{ // Stop copy other positions because we are finding any position:
break;
}
if( ! isset( $position_counts[ $source_Link->position ] ) )
{
$position_counts[ $source_Link->position ] = 0;
}
if( $position_counts[ $source_Link->position ] >= $limit_position )
{ // Skip this because of limit per position:
if( $source_position == $source_Link->position )
{ // Stop copy other positions because we are finding only current:
break;
}
else
{ // Continue to find other positions:
continue;
}
}
$position_counts[ $source_Link->position ]++;
$all_position_counts++;
}
if( $source_File = & $source_Link->get_File() )
{
if( isset( $dest_files_links[ $source_File->ID ] ) )
{ // Don't attach the File twice if it is already linked to the destination LinkOwner:
$new_link_ID = $dest_files_links[ $source_File->ID ];
}
else
{ // Attach new file because it is not linked to the destination LinkOwner yet:
$new_link_ID = $dest_LinkOwner->add_link( $source_Link->file_ID, ( empty( $dest_position ) ? $source_Link->position : $dest_position ), $dest_last_order++ );
}
$this->add_response( 'links', array(
'ID' => $new_link_ID,
'file_type' => $source_File->get_file_type(),
'orig_position' => $source_Link->position,
), 'array' );
}
}
}
/**** MODULE LINKS ---- END ****/
/**** MODULE TOOLS ---- START ****/
/**
* Call module to prepare request for tools
*/
private function module_tools()
{
if( empty( $this->args[1] ) )
{
$this->halt( 'Missing tool controller', 'wrong_request', 405 );
}
else
{
$tool_controller = $this->args[1];
if( ! method_exists( $this, 'controller_tool_'.$tool_controller ) )
{ // Unknown controller:
$this->halt( 'Unknown link controller "'.$tool_controller.'"', 'unknown_controller' );
// Exit here.
}
// Call collection controller to prepare current request:
$this->{'controller_tool_'.$tool_controller}();
}
}
/**
* Call tool controller to get available urlname
*/
private function controller_tool_available_urlname()
{
$urlname = param( 'urlname', 'string' );
$this->add_response( 'base', $urlname );
$this->add_response( 'urlname', urltitle_validate( $urlname, '', 0, false, 'blog_urlname', 'blog_ID', 'T_blogs' ) );
}
/**** MODULE TOOLS ---- END ****/
}