Файл: textpattern-4.5.7/textpattern/lib/txplib_wrapper.php
Строк: 727
<?php
/*
$HeadURL: https://textpattern.googlecode.com/svn/releases/4.5.7/source/textpattern/lib/txplib_wrapper.php $
$LastChangedRevision: 2931 $
*/
/**
 * Textpattern Wrapper Class for Textpattern 4.0.x
 *
 * Main goal for this class is to be used as a textpattern data wrapper by
 * any code which needs to have access to the textpattern articles data,
 * like XML-RPC, Atom, Moblogging or other external implementations.
 *
 * @link http://txp.kusor.com/wrapper
 * @author Pedro Palazon - http://kusor.net/
 * @copyright 2005-2008 The Textpattern Development Team - http://textpattern.com
 */
# This class requires to include some Textpattern files in order to work properly.
# See RPC Server implementation to view an example of the required files and predefined variables.
if (!defined('txpath')) die('txpath is undefined.');
include_once txpath.'/include/txp_auth.php';
# Include constants.php?
if (!defined('LEAVE_TEXT_UNTOUCHED')) define('LEAVE_TEXT_UNTOUCHED', 0);
if (!defined('USE_TEXTILE')) define('USE_TEXTILE', 1);
if (!defined('CONVERT_LINEBREAKS')) define('CONVERT_LINEBREAKS', 2);
class TXP_Wrapper
{
    /**
     * @var string The current user
     *
     * Remeber to use allways $this->txp_user when checking for permissions with this class
     */
    var $txp_user = null;
    /**
     * @var boolean Is the user authenticated
     */
    var $loggedin = false;
    /**
     * @var array Predefined Textpattern vars to be populated
     */
    var $vars = array(
        'ID','Title','Title_html','Body','Body_html','Excerpt','Excerpt_html','textile_excerpt','Image',
        'textile_body', 'Keywords','Status','Posted','Section','Category1','Category2',
        'Annotate','AnnotateInvite','AuthorID','Posted','override_form',
        'url_title','custom_1','custom_2','custom_3','custom_4','custom_5',
        'custom_6','custom_7','custom_8','custom_9','custom_10'
    );
    //Class constructor
    /**
     * Class constructor
     * @param string $txp_user the user login name
     * @param strign $txpass user password
     *
     * @see _validate
     */
    function TXP_Wrapper($txp_user, $txpass = NULL)
    {
        if ($this->_validate($txp_user, $txpass))
        {
            $this->txp_user = $txp_user;
            $this->loggedin = true;
        }
    }
    //Delete the article given the id
    /**
     * Delete the article given the id
     * @param mixed(string|integer) $article_id the ID of the article to delete
     * @return boolean true on success deletion
     */
    function deleteArticleID($article_id)
    {
        $article_id = assert_int($article_id);
        if ($this->loggedin && has_privs('article.delete', $this->txp_user)) {
            return safe_delete('textpattern', "ID = $article_id");
        }
        elseif ($this->loggedin && has_privs('article.delete.own', $this->txp_user))
        {
            $r = safe_field('ID', 'textpattern', "ID = $article_id AND AuthorID='".doSlash($this->txp_user)."'");
            if ($r || has_privs('article.delete', $this->txp_user))
            {
                return safe_delete('textpattern', "ID = $article_id");
            }
        }
        return false;
    }
    //Retrieves a list of articles matching the given criteria
    /**
     * Retrieves a list of articles matching the given criteria
     * @param string $what SQL column names to retrieve
     * @param string $where SQL condition to match
     * @param string $offset SQL offset
     * @param string $limit SQL limit
     * @param boolean $slash escape SQL column names and condition
     * @return mixed array on success, false on failure
     */
    function getArticleList($what='*', $where='1', $offset='0', $limit='10', $slash=true)
    {
        if ($this->loggedin && has_privs('article.edit.own', $this->txp_user))
        {
            $offset = assert_int($offset);
            $limit = assert_int($limit);
            if ($slash) {
                $where = doSlash($where);
                $what = doSlash($what);
            }
            if (has_privs('article.edit', $this->txp_user)) {
                $rs = safe_rows_start($what, 'textpattern', $where." order by Posted desc LIMIT $offset, $limit");
            }else{
                $rs = safe_rows_start($what, 'textpattern', $where." AND AuthorID='".doSlash($this->txp_user)."' order by Posted desc LIMIT $offset, $limit");
            }
            $out = array();
            if ($rs)
            {
                while ($a = nextRow($rs))
                {
                    $out[]= $a;
                }
            }
            return $out;
        }
        return false;
    }
    //Retrieves an article matching the given criteria
    /**
     * Retrieves an article matching the given criteria
     * @param string $what SQL column names to retrieve
     * @param string $where SQL condition to match
     * @param boolean $slash escape SQL column names and condition
     * @return mixed array on success, false on failure
     */
    function getArticle($what='*', $where='1', $slash=true)
    {
        if ($this->loggedin && has_privs('article.edit.own', $this->txp_user))
        {
            if ($slash) {
                $what  = doSlash($what);
                $where = doSlash($where);
            }
            // Higer user groups should be able to edit any article
            if (has_privs('article.edit', $this->txp_user)) {
                return safe_row($what, 'textpattern', $where);
            }else {
                // While restricted users should be able to edit their own articles only
                return safe_row($what, 'textpattern', $where." AND AuthorID='".doSlash($this->txp_user)."'");
            }
        }
        return false;
    }
    //Same thing, but handy shortcut known the ID
    /**
     * Same thing, but handy shortcut known the ID
     * @param mixed(string|integer) $article_id the ID of the article
     * @param string $what SQL column names to retrieve
     * @return mixed array on success, false on failure
     */
    function getArticleID($article_id, $what='*')
    {
        if ($this->loggedin && has_privs('article.edit.own', $this->txp_user))
        {
            $article_id = assert_int($article_id);
            if (has_privs('article.edit', $this->txp_user)) {
                return safe_row(doSlash($what), 'textpattern', "ID = $article_id");
            }else{
                return safe_row(doSlash($what), 'textpattern', "ID = $article_id AND AuthorID='".doSlash($this->txp_user)."'");
            }
        }
        return false;
    }
    //Updates an existing article
    /**
     * Updates an existing article
     * @param array $params the article fields to update
     * @param mixed(string|integer) $article_id the ID of the article to update
     * @return mixed integer article id on success, false on failure
     * @see _setArticle
     */
    function updateArticleID($article_id, $params)
    {
        $article_id = assert_int($article_id);
        $r = safe_field('ID', 'textpattern', "AuthorID='".doSlash($this->txp_user)."' AND ID = $article_id");
        if ($this->loggedin && $r && has_privs('article.edit.own', $this->txp_user))
        {    //Unprivileged user
            //Check if can edit published arts
            $r = assert_int($r);
            $oldstatus = safe_field('Status', 'textpattern', "ID = $r");
            if (($oldstatus=='4' || $oldstatus == '5') && !has_privs('article.edit.published', $this->txp_user)) return false;
            //If can, let's go
            return $this->_setArticle($params, $article_id);
        }
        elseif ($this->loggedin && has_privs('article.edit', $this->txp_user))
        {//Admin editing. Desires are behest.
            return $this->_setArticle($params, $article_id);
        }
        return false;
    }
    //Creates a new article
    /**
     * Creates a new article
     * @param array $params the article fields
     * @return mixed integer article id on success, false on failure
     * @see _setArticle
     */
    function newArticle($params)
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            //Prevent junior authors to publish articles
            if (($params['Status']=='4' || $params['Status']=='5') && !has_privs('article.publish', $this->txp_user))
            {
                $params['Status']='3';
            }
            return $this->_setArticle($params);
        }
        return false;
    }
    //Get full sections information
    /**
     * Get full sections information
     * @return mixed array on success, false on failure
     */
    function getSectionsList()
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            return safe_rows('*', 'txp_section',"name!='default'");
        }
        return false;
    }
    //Get one section
    /**
     * Get one section
     * @param string $name the section name
     * @return mixed array on success, false on failure
     */
    function getSection($name)
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            $name = doSlash($name);
            return safe_row('*', 'txp_section',"name='$name'");
        }
        return false;
    }
    //Get full categories information
    /**
     * Get full categories information
     * @return mixed array on success, false on failure
     */
    function getCategoryList()
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            return safe_rows('*', 'txp_category',"name!='root' AND type='article'");
        }
        return false;
    }
    /**
     * Get one category by category name
     * @param string $name the category name
     * @return mixed array on success, false on failure
     */
    function getCategory($name)
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            $name = doSlash($name);
            return safe_row('*', 'txp_category',"name='$name' AND type='article'");
        }
        return false;
    }
    /**
     * Get one category by category id
     * @param mixed(string|integer) $id category id
     * @return mixed array on success, false on failure
     */
    function getCategoryID($id)
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            $id = assert_int($id);
            return safe_row('*', 'txp_category',"id = $id");
        }
        return false;
    }
    /**
     * Get one category by category title
     * @param string $title the category title
     * @return mixed array on success, false on failure
     */
    function getCategoryTitle($title)
    {
        if ($this->loggedin && has_privs('article', $this->txp_user))
        {
            $title = doSlash($title);
            return safe_row('*', 'txp_category',"title='$title' AND type='article'");
        }
        return false;
    }
    //Get full information for current user
    /**
     * Get full information for current user
     * @return mixed array on success, false on failure
     */
    function getUser()
    {
        if ($this->loggedin)
        {
            return safe_row('*', 'txp_users',"name='$this->txp_user'");
        }
        return false;
    }
    //Retrieves a template with the given name
    /**
     * Retrieves a template with the given name
     * @param string $name the template name
     */
    function getTemplate($name)
    {
        if ($this->loggedin && has_privs('page', $this->txp_user))
        {
            $name = doSlash($name);
            return safe_field('user_html', 'txp_page', "name='$name'");
        }
        return false;
    }
    //Updates a template with the given name
    /**
     * Updates a template with the given name
     * @param string $name the template name
     * @param string $html the template contents
     * @return boolean true on success
     */
    function setTemplate($name, $html)
    {
        if ($this->loggedin && has_privs('page', $this->txp_user))
        {
            $name = doSlash($name);
            $html = doSlash($html);
            return safe_update('txp_page', "user_html='$html'", "name='$name'");
        }
        return false;
    }
    // Intended to update article non content fields, like categories
    // section or Keywords
    /**
     * Intended to update article non content fields, like categories, section or Keywords
     * @param mixed(string|integer) $article_id the ID of the article to update
     * @param string $field the name of the field to update
     * @param mixed $value desired value for that field
     * @return boolean true on success
     */
    function updateArticleField($article_id, $field, $value)
    {
        $disallow = array('Body','Body_html','Title','Title_html','Excerpt',
                    'Excerpt_html','textile_excerpt','textile_body','LastMod',
                    'LastModID', 'feed_time', 'uid');
        if ($this->loggedin && has_privs('article.edit', $this->txp_user) && !in_array(doSlash($field),$disallow))
        {
            $field = doSlash($field);
            $value = doSlash($value);
            if($field == 'Posted')
            {
                $value = strtotime($value)-tz_offset();
                $value = "from_unixtime($value)";
                $sql = "Posted = $value";
            }elseif ($field == 'Status'){
                $value = assert_int($value);
                if (!has_privs('article.publish', $this->txp_user) && $value >=4) $value = 3;
                $sql = "Status = $value";
            }else{
                $sql = "$field='$value'";
            }
            $sql.= ", LastMod = now(),
                    LastModID = '$this->txp_user'";
            $article_id = assert_int($article_id);
            $rs = safe_update('textpattern', $sql, "ID = $article_id");
            //Do not update lastmod pref here. No new content at all.
            return $rs;
        }
        return false;
    }
// -------------------------------------------------------------
// Private. Real action takes place here.
// -------------------------------------------------------------
    /**
     * Executes the real action for @see udpateArticleId and @see newArticle
     * @param array $incoming containing the desired article fields
     * @param mixed(string|integer) $article_id the ID of the article to update
     * @return mixed integer article id on success, false otherwise
     * @access private
     */
    function _setArticle($incoming, $article_id = null)
    {
        global $txpcfg;
        $prefs = get_prefs();
        extract($prefs);
        if (!empty($incoming['Section']) && !$this->getSection($incoming['Section'])) {
            return false;
        }
        if (!empty($incoming['Category1']) && !$this->getCategory($incoming['Category1'])) {
            return false;
        }
        if (!empty($incoming['Category2']) && !$this->getCategory($incoming['Category2'])) {
            return false;
        }
        if ($article_id!==null) {
            $article_id = assert_int($article_id);
        }
        //All validation rules assumed to be passed before this point.
        //Do content processing here
        $incoming_with_markup = $this->textile_main_fields($incoming, $use_textile);
        $incoming['Title'] = $incoming_with_markup['Title'];
        if (empty($incoming['Body_html']) && !empty($incoming['Body']))
        {
            $incoming['Body_html'] = $incoming_with_markup['Body_html'];
        }
        if (empty($incoming['Excerpt_html']) && !empty($incoming['Excerpt']))
        {
                $incoming['Excerpt_html'] = $incoming_with_markup['Excerpt_html'];
        }
        unset($incoming_with_markup);
        if (empty($incoming['Posted'])) {
            if ($article_id===null) {
                $when = (!$article_id)? 'now()': '';
                $incoming['Posted'] = $when;
            }else{
                # do not override post time for existing articles unless Posted is present
                unset($incoming['Posted']);
            }
        } else {
            $when = strtotime($incoming['Posted'])-tz_offset();
            $when = "from_unixtime($when)";
        }
        if ($incoming['Title'] || $incoming['Body'] || $incoming['Excerpt']) {
            //Build SQL then and run query
            //Prevent data erase if not defined on the update action
            //but it was on the DB from a previous creation/edition time
            if ($article_id){
                $old = safe_row('*','textpattern', "ID = $article_id");
                //Status should be defined previously. Be sure of that.
                if (!has_privs('article.publish', $this->txp_user) && $incoming['Status']==4 && $old['Status']!=4) $incoming['Status'] = 3;
                foreach ($old as $key=>$val)
                {
                     if (!isset($incoming[$key])) $incoming[$key] = $val;
                }
            }else{
                //Status should be defined previously. Be sure of that.
                if (!has_privs('article.publish', $this->txp_user) && $incoming['Status']==4) $incoming['Status'] = 3;
            }
            if (empty($incoming['Section']) && $article_id)
            {
                $incoming['Section'] = safe_field('Section','textpattern',"ID = $article_id");
            }
            $incoming = $this->_check_keys($incoming,
                array(
                    'AuthorID' => $this->txp_user,
                    'Annotate' => $comments_on_default,
                    'AnnotateInvite' => $comments_default_invite,
                    'textile_body' => $use_textile,
                    'textile_excerpt' => $use_textile,
                    'url_title' => stripSpace($incoming['Title'])
                )
            );
            //Build the SQL query
            $sql = array();
            foreach ($incoming as $key => $val)
            {
                if($key == 'Posted' && $val == 'now()')
                {
                    $sql[]= "$key = $val";
                }elseif ($key!='ID' && $key!='uid' && $key!='feed_time' && $key!='LastMod' && $key!='LastModID')
                {
                    $sql[]= "$key = '".doSlash($val)."'";
                }
            }
            $sql[]= 'LastMod = now()';
            $sql[]= "LastModID = '".doSlash($this->txp_user)."'";
            if (!$article_id) $sql[]= "uid = '".doSlash(md5(uniqid(rand(),true)))."'";
            if (!$article_id)
            {
                if (empty($incoming['Posted']))
                {
                    $sql[]= "feed_time = curdate()";
                }else{
                    $when = strtotime($incoming['Posted'])-tz_offset();
                    $when = strftime("%Y-%m-%d", $when);
                    $sql[]= "feed_time ='".doSlash($when)."'";
                }
            }
            $sql = join(', ', $sql);
            $rs = ($article_id)?
                   safe_update('textpattern', $sql, "ID = $article_id"):
                   safe_insert('textpattern', $sql);
           $oldstatus = ($article_id)? $old['Status'] : '';
           if (!$article_id && $rs) $article_id = $rs;
           if (($incoming['Status']>=4 && !$article_id) || ($oldstatus!=4 && $article_id)) {
                safe_update("txp_prefs", "val = now()", "name = 'lastmod'");
                //@$this->_sendPings();
           }
           return $article_id;
        }
        return false;
    }
// -------------------------------------------------------------
// Private
// -------------------------------------------------------------
    /**
     * Attemp to validates the user with the provided password
     * or takes it from the global scope, assuming the user is logged in
     * @param string $user login name of the user to validate
     * @param string(optional) $password for that user
     * @access private
     * @return boolean, true if user is logged in
     */
    function _validate($user,$password = NULL) {
        if ($password!==NULL)
        {
            $r = txp_validate($user, $password);
        }else{
            $r = true;
        }
        if ($r) {
            // update the last access time
            $safe_user = addslashes($user);
            safe_update("txp_users", "last_access = now()", "name = '$safe_user'");
            return true;
        }
        return false;
    }
// -------------------------------------------------------------
// Keep this apart for now. Maybe future changes ob this?
// -------------------------------------------------------------
// This is duplicated code from txp_article.php too
    function _sendPings()
    {
        global $prefs, $txpcfg;
        extract($prefs);
        include_once txpath.'/lib/IXRClass.php';
        if ($ping_textpattern_com) {
            $tx_client = new IXR_Client('http://textpattern.com/xmlrpc/');
            $tx_client->query('ping.Textpattern', $sitename, hu);
        }
        if ($ping_weblogsdotcom==1) {
            $wl_client = new IXR_Client('http://rpc.pingomatic.com/');
            $wl_client->query('weblogUpdates.ping', $sitename, hu);
        }
    }
    /**
     * Check if the given parameters are the appropiated ones for the articles
     * @access private
     * @param $incoming array the incoming associative array
     * @param $default associative array containing default values for the desired keys
     * @return array properly striped off the fields which don't match the defined ones.
     */
    function _check_keys($incoming, $default = array())
    {
        $out = array();
        # strip off unsuited keys
        foreach ($incoming as $key => $val)
        {
            if (in_array($key, $this->vars))
            {
                $out[$key] = $val;
            }
        }
        foreach ($this->vars as $def_key)
        {
            # Add those ones inexistent in the incoming array
            if (!array_key_exists($def_key,$out))
            {
                $out[$def_key] = '';
            }
            # setup the provided default value, if any, only when the incoming value is empty
            if (array_key_exists($def_key, $default) && empty($out[$def_key]))
            {
                $out[$def_key] = $default[$def_key];
            }
        }
        return $out;
    }
    /**
     * Apply textile to the main article fields
     * (duplicated from txp_article.php!)
     * @param array containing the $incoming vars array
     * @param global use_textile preference
     * @return array the same one containing the formatted fields
     */
    function textile_main_fields($incoming, $use_textile = 1)
    {
        global $txpcfg;
        include_once txpath.'/lib/classTextile.php';
        $textile = new Textile();
        if (!empty($event) and $event == 'article')
        {
            $incoming['Title_plain'] = $incoming['Title'];
        }
        if ($incoming['textile_body'] == USE_TEXTILE)
        {
            $incoming['Title'] = $textile->TextileThis($incoming['Title'],'',1);
        }
        $incoming['url_title'] = preg_replace('|[x00-x1f#%+/?x7f]|', '', $incoming['url_title']);
        $incoming['Body_html'] = TXP_Wrapper::format_field($incoming['Body'],$incoming['textile_body'],$textile);
        $incoming['Excerpt_html'] = TXP_Wrapper::format_field($incoming['Excerpt'],$incoming['textile_excerpt'],$textile);
        return $incoming;
    }
    # Try to avoid code duplication when formating fields.
    /**
     * Apply markup to a given fields
     *
     * @param string $field raw field contents
     * @param integer $format format type to apply
     * @param object $textile instance
     * @return string html formated field
     */
    function format_field($field, $format,$textile)
    {
        switch ($format){
            case LEAVE_TEXT_UNTOUCHED: $html = trim($field); break;
            case CONVERT_LINEBREAKS: $html = nl2br(trim($field)); break;
            case USE_TEXTILE:
                $html = $textile->TextileThis($field);
            break;
        }
        return $html;
    }
}
?>