diff --git a/api-changes-1.1.1/CHANGES b/api-changes-1.1.1/CHANGES index eaf72f2..60e39b3 100644 --- a/api-changes-1.1.1/CHANGES +++ b/api-changes-1.1.1/CHANGES @@ -6,10 +6,11 @@ * actions/apicheckhub.php New api action (not used yet) * actions/apiexternalprofileshow.php New api action * actions/apigroupadmins.php New api action + * actions/apiaccountupdatelinkcolor.php New api action * lib/apiaction.php - - add urls to larger avatars and groupcount field + - add urls to larger avatars, groupcount and linkcolor field ~LINE 213 $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); @@ -23,6 +24,7 @@ Avatar::defaultImage(AVATAR_PROFILE_SIZE); $twitter_user['groups_count'] = $profile->getGroups(0, null)->N; + $twitter_user['linkcolor'] = $user->linkcolor; - add the uri-field @@ -85,6 +87,9 @@ array('action' => 'ApiGroupAdmins', 'id' => Nickname::INPUT_FMT, 'format' => '(xml|json)')); + + $m->connect('api/account/update_link_color.json', + array('action' => 'ApiAccountUpdateLinkColor')); - also, tags need regexp to work with unicode charachters, e.g. farsi and arabic: @@ -173,3 +178,12 @@ // $final = common_shorten_links($content); // } + + + * classes/User.php + + - add field "linkcolor" to user table (do this is the db too!) + + ~ line 64 public $linkcolor; // varchar(6) + ~ line 105 'linkcolor' => array('type' => 'varchar', 'length' => 6, 'description' => 'hex link color'), + diff --git a/api-changes-1.1.1/actions/apiaccountupdatelinkcolor.php b/api-changes-1.1.1/actions/apiaccountupdatelinkcolor.php new file mode 100644 index 0000000..7bf763f --- /dev/null +++ b/api-changes-1.1.1/actions/apiaccountupdatelinkcolor.php @@ -0,0 +1,109 @@ +. + * + * @category API + * @package StatusNet + * @author Hannes Mannerheim + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + + +class ApiAccountUpdateLinkColorAction extends ApiAuthAction +{ + var $linkcolor = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + */ + function prepare($args) + { + parent::prepare($args); + + $this->user = $this->auth_user; + + $this->linkcolor = $this->trimmed('linkcolor'); + + return true; + } + + /** + * Handle the request + * + * Try to save the user's colors in her design. Create a new design + * if the user doesn't already have one. + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, $this->format + ); + return; + } + + $validhex = preg_match('/^[a-f0-9]{6}$/i',$this->linkcolor); + if($validhex === false || $validhex == 0) { + $this->clientError(_('Not a valid hex color.'),404,'json'); + return; + } + + // save the new color + $original = clone($this->user); + $this->user->linkcolor = $this->linkcolor; + if (!$this->user->update($original)) { + $this->clientError(_('Error updating user.'),404,'json'); + return; + } + + $profile = $this->user->getProfile(); + + if (empty($profile)) { + $this->clientError(_('User has no profile.'),'json'); + return; + } + + $twitter_user = $this->twitterUserArray($profile, true); + + $this->initDocument('json'); + $this->showJsonObjects($twitter_user); + $this->endDocument('json'); + } + + +} diff --git a/api-changes-1.1.1/classes/User.php b/api-changes-1.1.1/classes/User.php new file mode 100644 index 0000000..b72a831 --- /dev/null +++ b/api-changes-1.1.1/classes/User.php @@ -0,0 +1,1218 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Table Definition for user + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +require_once 'Validate.php'; + +class User extends Managed_DataObject +{ + const SUBSCRIBE_POLICY_OPEN = 0; + const SUBSCRIBE_POLICY_MODERATE = 1; + + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user'; // table name + public $id; // int(4) primary_key not_null + public $nickname; // varchar(64) unique_key + public $password; // varchar(255) + public $email; // varchar(255) unique_key + public $incomingemail; // varchar(255) unique_key + public $emailnotifysub; // tinyint(1) default_1 + public $emailnotifyfav; // tinyint(1) default_1 + public $emailnotifynudge; // tinyint(1) default_1 + public $emailnotifymsg; // tinyint(1) default_1 + public $emailnotifyattn; // tinyint(1) default_1 + public $emailmicroid; // tinyint(1) default_1 + public $language; // varchar(50) + public $timezone; // varchar(50) + public $emailpost; // tinyint(1) default_1 + public $sms; // varchar(64) unique_key + public $carrier; // int(4) + public $smsnotify; // tinyint(1) + public $smsreplies; // tinyint(1) + public $smsemail; // varchar(255) + public $uri; // varchar(255) unique_key + public $autosubscribe; // tinyint(1) + public $subscribe_policy; // tinyint(1) + public $urlshorteningservice; // varchar(50) default_ur1.ca + public $inboxed; // tinyint(1) + public $linkcolor; // varchar(6) + public $background_image_url; // varchar(255) + public $private_stream; // tinyint(1) default_0 + public $created; // datetime() not_null + public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + public static function schemaDef() + { + return array( + 'description' => 'local users', + 'fields' => array( + 'id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), + 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname or username, duped in profile'), + 'password' => array('type' => 'varchar', 'length' => 255, 'description' => 'salted password, can be null for OpenID users'), + 'email' => array('type' => 'varchar', 'length' => 255, 'description' => 'email address for password recovery etc.'), + 'incomingemail' => array('type' => 'varchar', 'length' => 255, 'description' => 'email address for post-by-email'), + 'emailnotifysub' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of subscriptions'), + 'emailnotifyfav' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of favorites'), + 'emailnotifynudge' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of nudges'), + 'emailnotifymsg' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of direct messages'), + 'emailnotifyattn' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of @-replies'), + 'emailmicroid' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'whether to publish email microid'), + 'language' => array('type' => 'varchar', 'length' => 50, 'description' => 'preferred language'), + 'timezone' => array('type' => 'varchar', 'length' => 50, 'description' => 'timezone'), + 'emailpost' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Post by email'), + 'sms' => array('type' => 'varchar', 'length' => 64, 'description' => 'sms phone number'), + 'carrier' => array('type' => 'int', 'description' => 'foreign key to sms_carrier'), + 'smsnotify' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'whether to send notices to SMS'), + 'smsreplies' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'whether to send notices to SMS on replies'), + 'smsemail' => array('type' => 'varchar', 'length' => 255, 'description' => 'built from sms and carrier'), + 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), + 'autosubscribe' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'automatically subscribe to users who subscribe to us'), + 'subscribe_policy' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => '0 = anybody can subscribe; 1 = require approval'), + 'urlshorteningservice' => array('type' => 'varchar', 'length' => 50, 'default' => 'internal', 'description' => 'service to use for auto-shortening URLs'), + 'inboxed' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'has an inbox been created for this user?'), + 'linkcolor' => array('type' => 'varchar', 'length' => 6, 'description' => 'hex link color'), + 'background_image_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'url to profile image'), + 'private_stream' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'whether to limit all notices to followers only'), + + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'user_nickname_key' => array('nickname'), + 'user_email_key' => array('email'), + 'user_incomingemail_key' => array('incomingemail'), + 'user_sms_key' => array('sms'), + 'user_uri_key' => array('uri'), + ), + 'foreign keys' => array( + 'user_id_fkey' => array('profile', array('id' => 'id')), + 'user_carrier_fkey' => array('sms_carrier', array('carrier' => 'id')), + ), + 'indexes' => array( + 'user_smsemail_idx' => array('smsemail'), + ), + ); + } + + protected $_profile = -1; + + /** + * @return Profile + */ + function getProfile() + { + if (is_int($this->_profile) && $this->_profile == -1) { // invalid but distinct from null + $this->_profile = Profile::staticGet('id', $this->id); + if (empty($this->_profile)) { + throw new UserNoProfileException($this); + } + } + + return $this->_profile; + } + + function isSubscribed($other) + { + $profile = $this->getProfile(); + return $profile->isSubscribed($other); + } + + function hasPendingSubscription($other) + { + $profile = $this->getProfile(); + return $profile->hasPendingSubscription($other); + } + + // 'update' won't write key columns, so we have to do it ourselves. + + function updateKeys(&$orig) + { + $this->_connect(); + $parts = array(); + foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { + if (strcmp($this->$k, $orig->$k) != 0) { + $parts[] = $k . ' = ' . $this->_quote($this->$k); + } + } + if (count($parts) == 0) { + // No changes + return true; + } + $toupdate = implode(', ', $parts); + + $table = common_database_tablename($this->tableName()); + $qry = 'UPDATE ' . $table . ' SET ' . $toupdate . + ' WHERE id = ' . $this->id; + $orig->decache(); + $result = $this->query($qry); + if ($result) { + $this->encache(); + } + return $result; + } + + /** + * Check whether the given nickname is potentially usable, or if it's + * excluded by any blacklists on this system. + * + * WARNING: INPUT IS NOT VALIDATED OR NORMALIZED. NON-NORMALIZED INPUT + * OR INVALID INPUT MAY LEAD TO FALSE RESULTS. + * + * @param string $nickname + * @return boolean true if clear, false if blacklisted + */ + static function allowed_nickname($nickname) + { + // XXX: should already be validated for size, content, etc. + $blacklist = common_config('nickname', 'blacklist'); + + //all directory and file names should be blacklisted + $d = dir(INSTALLDIR); + while (false !== ($entry = $d->read())) { + $blacklist[]=$entry; + } + $d->close(); + + //all top level names in the router should be blacklisted + $router = Router::get(); + foreach(array_keys($router->m->getPaths()) as $path){ + if(preg_match('/^\/(.*?)[\/\?]/',$path,$matches)){ + $blacklist[]=$matches[1]; + } + } + return !in_array($nickname, $blacklist); + } + + /** + * Get the most recent notice posted by this user, if any. + * + * @return mixed Notice or null + */ + function getCurrentNotice() + { + $profile = $this->getProfile(); + return $profile->getCurrentNotice(); + } + + function getCarrier() + { + return Sms_carrier::staticGet('id', $this->carrier); + } + + /** + * @deprecated use Subscription::start($sub, $other); + */ + function subscribeTo($other) + { + return Subscription::start($this->getProfile(), $other); + } + + function hasBlocked($other) + { + $profile = $this->getProfile(); + return $profile->hasBlocked($other); + } + + /** + * Register a new user account and profile and set up default subscriptions. + * If a new-user welcome message is configured, this will be sent. + * + * @param array $fields associative array of optional properties + * string 'bio' + * string 'email' + * bool 'email_confirmed' pass true to mark email as pre-confirmed + * string 'fullname' + * string 'homepage' + * string 'location' informal string description of geolocation + * float 'lat' decimal latitude for geolocation + * float 'lon' decimal longitude for geolocation + * int 'location_id' geoname identifier + * int 'location_ns' geoname namespace to interpret location_id + * string 'nickname' REQUIRED + * string 'password' (may be missing for eg OpenID registrations) + * string 'code' invite code + * ?string 'uri' permalink to notice; defaults to local notice URL + * @return mixed User object or false on failure + */ + static function register($fields) { + + // MAGICALLY put fields into current scope + + extract($fields); + + $profile = new Profile(); + + if(!empty($email)) + { + $email = common_canonical_email($email); + } + + $nickname = common_canonical_nickname($nickname); + $profile->nickname = $nickname; + if(! User::allowed_nickname($nickname)){ + common_log(LOG_WARNING, sprintf("Attempted to register a nickname that is not allowed: %s", $profile->nickname), + __FILE__); + return false; + } + $profile->profileurl = common_profile_url($nickname); + + if (!empty($fullname)) { + $profile->fullname = $fullname; + } + if (!empty($homepage)) { + $profile->homepage = $homepage; + } + if (!empty($bio)) { + $profile->bio = $bio; + } + if (!empty($location)) { + $profile->location = $location; + + $loc = Location::fromName($location); + + if (!empty($loc)) { + $profile->lat = $loc->lat; + $profile->lon = $loc->lon; + $profile->location_id = $loc->location_id; + $profile->location_ns = $loc->location_ns; + } + } + + $profile->created = common_sql_now(); + + $user = new User(); + + $user->nickname = $nickname; + + $invite = null; + + // Users who respond to invite email have proven their ownership of that address + + if (!empty($code)) { + $invite = Invitation::staticGet($code); + if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) { + $user->email = $invite->address; + } + } + + if(isset($email_confirmed) && $email_confirmed) { + $user->email = $email; + } + + // This flag is ignored but still set to 1 + + $user->inboxed = 1; + + // Set default-on options here, otherwise they'll be disabled + // initially for sites using caching, since the initial encache + // doesn't know about the defaults in the database. + $user->emailnotifysub = 1; + $user->emailnotifyfav = 1; + $user->emailnotifynudge = 1; + $user->emailnotifymsg = 1; + $user->emailnotifyattn = 1; + $user->emailmicroid = 1; + $user->emailpost = 1; + $user->jabbermicroid = 1; + + $user->created = common_sql_now(); + + if (Event::handle('StartUserRegister', array(&$user, &$profile))) { + + $profile->query('BEGIN'); + + $id = $profile->insert(); + + if (empty($id)) { + common_log_db_error($profile, 'INSERT', __FILE__); + return false; + } + + $user->id = $id; + + if (!empty($uri)) { + $user->uri = $uri; + } else { + $user->uri = common_user_uri($user); + } + + if (!empty($password)) { // may not have a password for OpenID users + $user->password = common_munge_password($password, $id); + } + + $result = $user->insert(); + + if (!$result) { + common_log_db_error($user, 'INSERT', __FILE__); + return false; + } + + // Everyone gets an inbox + + $inbox = new Inbox(); + + $inbox->user_id = $user->id; + $inbox->notice_ids = ''; + + $result = $inbox->insert(); + + if (!$result) { + common_log_db_error($inbox, 'INSERT', __FILE__); + return false; + } + + // Everyone is subscribed to themself + + $subscription = new Subscription(); + $subscription->subscriber = $user->id; + $subscription->subscribed = $user->id; + $subscription->created = $user->created; + + $result = $subscription->insert(); + + if (!$result) { + common_log_db_error($subscription, 'INSERT', __FILE__); + return false; + } + + // Mark that this invite was converted + + if (!empty($invite)) { + $invite->convert($user); + } + + if (!empty($email) && !$user->email) { + + $confirm = new Confirm_address(); + $confirm->code = common_confirmation_code(128); + $confirm->user_id = $user->id; + $confirm->address = $email; + $confirm->address_type = 'email'; + + $result = $confirm->insert(); + + if (!$result) { + common_log_db_error($confirm, 'INSERT', __FILE__); + return false; + } + } + + if (!empty($code) && $user->email) { + $user->emailChanged(); + } + + // Default system subscription + + $defnick = common_config('newuser', 'default'); + + if (!empty($defnick)) { + $defuser = User::staticGet('nickname', $defnick); + if (empty($defuser)) { + common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick), + __FILE__); + } else { + Subscription::start($user, $defuser); + } + } + + $profile->query('COMMIT'); + + if (!empty($email) && !$user->email) { + mail_confirm_address($user, $confirm->code, $profile->nickname, $email); + } + + // Welcome message + + $welcome = common_config('newuser', 'welcome'); + + if (!empty($welcome)) { + $welcomeuser = User::staticGet('nickname', $welcome); + if (empty($welcomeuser)) { + common_log(LOG_WARNING, sprintf("Welcome user %s does not exist.", $defnick), + __FILE__); + } else { + $notice = Notice::saveNew($welcomeuser->id, + // TRANS: Notice given on user registration. + // TRANS: %1$s is the sitename, $2$s is the registering user's nickname. + sprintf(_('Welcome to %1$s, @%2$s!'), + common_config('site', 'name'), + $user->nickname), + 'system'); + } + } + + Event::handle('EndUserRegister', array(&$profile, &$user)); + } + + return $user; + } + + // Things we do when the email changes + function emailChanged() + { + + $invites = new Invitation(); + $invites->address = $this->email; + $invites->address_type = 'email'; + + if ($invites->find()) { + while ($invites->fetch()) { + $other = User::staticGet($invites->user_id); + subs_subscribe_to($other, $this); + } + } + } + + function hasFave($notice) + { + $profile = $this->getProfile(); + return $profile->hasFave($notice); + } + + function mutuallySubscribed($other) + { + $profile = $this->getProfile(); + return $profile->mutuallySubscribed($other); + } + + function mutuallySubscribedUsers() + { + // 3-way join; probably should get cached + $UT = common_config('db','type')=='pgsql'?'"user"':'user'; + $qry = "SELECT $UT.* " . + "FROM subscription sub1 JOIN $UT ON sub1.subscribed = $UT.id " . + "JOIN subscription sub2 ON $UT.id = sub2.subscriber " . + 'WHERE sub1.subscriber = %d and sub2.subscribed = %d ' . + "ORDER BY $UT.nickname"; + $user = new User(); + $user->query(sprintf($qry, $this->id, $this->id)); + + return $user; + } + + function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return Reply::stream($this->id, $offset, $limit, $since_id, $before_id); + } + + function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { + $profile = $this->getProfile(); + return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id); + } + + function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + $profile = $this->getProfile(); + return $profile->getNotices($offset, $limit, $since_id, $before_id); + } + + function favoriteNotices($own=false, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) + { + return Fave::stream($this->id, $offset, $limit, $own, $since_id, $max_id); + } + + function noticeInbox($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + $stream = new InboxNoticeStream($this); + return $stream->getNotices($offset, $limit, $since_id, $before_id); + } + + // DEPRECATED, use noticeInbox() + + function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return $this->noticeInbox($offset, $limit, $since_id, $before_id); + } + + // DEPRECATED, use noticeInbox() + + function noticesWithFriendsThreaded($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return $this->noticeInbox($offset, $limit, $since_id, $before_id); + } + + // DEPRECATED, use noticeInbox() + + function noticeInboxThreaded($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return $this->noticeInbox($offset, $limit, $since_id, $before_id); + } + + // DEPRECATED, use noticeInbox() + + function friendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return $this->noticeInbox($offset, $limit, $since_id, $before_id); + } + + // DEPRECATED, use noticeInbox() + + function ownFriendsTimeline($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + $this->noticeInbox($offset, $limit, $since_id, $before_id); + } + + function blowFavesCache() + { + $profile = $this->getProfile(); + $profile->blowFavesCache(); + } + + function getSelfTags() + { + return Profile_tag::getTagsArray($this->id, $this->id, $this->id); + } + + function setSelfTags($newtags, $privacy) + { + return Profile_tag::setTags($this->id, $this->id, $newtags, $privacy); + } + + function block($other) + { + // Add a new block record + + // no blocking (and thus unsubbing from) yourself + + if ($this->id == $other->id) { + common_log(LOG_WARNING, + sprintf( + "Profile ID %d (%s) tried to block themself.", + $this->id, + $this->nickname + ) + ); + return false; + } + + $block = new Profile_block(); + + // Begin a transaction + + $block->query('BEGIN'); + + $block->blocker = $this->id; + $block->blocked = $other->id; + + $result = $block->insert(); + + if (!$result) { + common_log_db_error($block, 'INSERT', __FILE__); + return false; + } + + $self = $this->getProfile(); + if (Subscription::exists($other, $self)) { + Subscription::cancel($other, $self); + } + if (Subscription::exists($self, $other)) { + Subscription::cancel($self, $other); + } + + $block->query('COMMIT'); + + return true; + } + + function unblock($other) + { + // Get the block record + + $block = Profile_block::get($this->id, $other->id); + + if (!$block) { + return false; + } + + $result = $block->delete(); + + if (!$result) { + common_log_db_error($block, 'DELETE', __FILE__); + return false; + } + + return true; + } + + function isMember($group) + { + $profile = $this->getProfile(); + return $profile->isMember($group); + } + + function isAdmin($group) + { + $profile = $this->getProfile(); + return $profile->isAdmin($group); + } + + function getGroups($offset=0, $limit=null) + { + $profile = $this->getProfile(); + return $profile->getGroups($offset, $limit); + } + + /** + * Request to join the given group. + * May throw exceptions on failure. + * + * @param User_group $group + * @return Group_member + */ + function joinGroup(User_group $group) + { + $profile = $this->getProfile(); + return $profile->joinGroup($group); + } + + /** + * Leave a group that this user is a member of. + * + * @param User_group $group + */ + function leaveGroup(User_group $group) + { + $profile = $this->getProfile(); + return $profile->leaveGroup($group); + } + + function getSubscriptions($offset=0, $limit=null) + { + $profile = $this->getProfile(); + return $profile->getSubscriptions($offset, $limit); + } + + function getSubscribers($offset=0, $limit=null) + { + $profile = $this->getProfile(); + return $profile->getSubscribers($offset, $limit); + } + + function getTaggedSubscribers($tag, $offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscriber ' . + 'JOIN profile_tag ON (profile_tag.tagged = subscription.subscriber ' . + 'AND profile_tag.tagger = subscription.subscribed) ' . + 'WHERE subscription.subscribed = %d ' . + "AND profile_tag.tag = '%s' " . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + if ($offset) { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } + + $profile = new Profile(); + + $cnt = $profile->query(sprintf($qry, $this->id, $profile->escape($tag))); + + return $profile; + } + + function getTaggedSubscriptions($tag, $offset=0, $limit=null) + { + $qry = + 'SELECT profile.* ' . + 'FROM profile JOIN subscription ' . + 'ON profile.id = subscription.subscribed ' . + 'JOIN profile_tag on (profile_tag.tagged = subscription.subscribed ' . + 'AND profile_tag.tagger = subscription.subscriber) ' . + 'WHERE subscription.subscriber = %d ' . + "AND profile_tag.tag = '%s' " . + 'AND subscription.subscribed != subscription.subscriber ' . + 'ORDER BY subscription.created DESC '; + + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + + $profile = new Profile(); + + $profile->query(sprintf($qry, $this->id, $profile->escape($tag))); + + return $profile; + } + + function hasRight($right) + { + $profile = $this->getProfile(); + return $profile->hasRight($right); + } + + function delete() + { + try { + $profile = $this->getProfile(); + $profile->delete(); + } catch (UserNoProfileException $unp) { + common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion."); + } + + $related = array('Fave', + 'Confirm_address', + 'Remember_me', + 'Foreign_link', + 'Invitation', + ); + + Event::handle('UserDeleteRelated', array($this, &$related)); + + foreach ($related as $cls) { + $inst = new $cls(); + $inst->user_id = $this->id; + $inst->delete(); + } + + $this->_deleteTags(); + $this->_deleteBlocks(); + + parent::delete(); + } + + function _deleteTags() + { + $tag = new Profile_tag(); + $tag->tagger = $this->id; + $tag->delete(); + } + + function _deleteBlocks() + { + $block = new Profile_block(); + $block->blocker = $this->id; + $block->delete(); + // XXX delete group block? Reset blocker? + } + + function hasRole($name) + { + $profile = $this->getProfile(); + return $profile->hasRole($name); + } + + function grantRole($name) + { + $profile = $this->getProfile(); + return $profile->grantRole($name); + } + + function revokeRole($name) + { + $profile = $this->getProfile(); + return $profile->revokeRole($name); + } + + function isSandboxed() + { + $profile = $this->getProfile(); + return $profile->isSandboxed(); + } + + function isSilenced() + { + $profile = $this->getProfile(); + return $profile->isSilenced(); + } + + function repeatedByMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $stream = new RepeatedByMeNoticeStream($this); + return $stream->getNotices($offset, $limit, $since_id, $max_id); + } + + + function repeatsOfMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + $stream = new RepeatsOfMeNoticeStream($this); + + return $stream->getNotices($offset, $limit, $since_id, $max_id); + } + + + function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) + { + // TRANS: Exception thrown when trying view "repeated to me". + throw new Exception(_('Not implemented since inbox change.')); + } + + function shareLocation() + { + $cfg = common_config('location', 'share'); + + if ($cfg == 'always') { + return true; + } else if ($cfg == 'never') { + return false; + } else { // user + $share = true; + + $prefs = User_location_prefs::staticGet('user_id', $this->id); + + if (empty($prefs)) { + $share = common_config('location', 'sharedefault'); + } else { + $share = $prefs->share_location; + $prefs->free(); + } + + return $share; + } + } + + static function siteOwner() + { + $owner = self::cacheGet('user:site_owner'); + + if ($owner === false) { // cache miss + + $pr = new Profile_role(); + + $pr->role = Profile_role::OWNER; + + $pr->orderBy('created'); + + $pr->limit(1); + + if ($pr->find(true)) { + $owner = User::staticGet('id', $pr->profile_id); + } else { + $owner = null; + } + + self::cacheSet('user:site_owner', $owner); + } + + return $owner; + } + + /** + * Pull the primary site account to use in single-user mode. + * If a valid user nickname is listed in 'singleuser':'nickname' + * in the config, this will be used; otherwise the site owner + * account is taken by default. + * + * @return User + * @throws ServerException if no valid single user account is present + * @throws ServerException if called when not in single-user mode + */ + static function singleUser() + { + if (common_config('singleuser', 'enabled')) { + + $user = null; + + $nickname = common_config('singleuser', 'nickname'); + + if (!empty($nickname)) { + $user = User::staticGet('nickname', $nickname); + } + + // if there was no nickname or no user by that nickname, + // try the site owner. + + if (empty($user)) { + $user = User::siteOwner(); + } + + if (!empty($user)) { + return $user; + } else { + // TRANS: Server exception. + throw new ServerException(_('No single user defined for single-user mode.')); + } + } else { + // TRANS: Server exception. + throw new ServerException(_('Single-user mode code called when not enabled.')); + } + } + + /** + * This is kind of a hack for using external setup code that's trying to + * build single-user sites. + * + * Will still return a username if the config singleuser/nickname is set + * even if the account doesn't exist, which normally indicates that the + * site is horribly misconfigured. + * + * At the moment, we need to let it through so that router setup can + * complete, otherwise we won't be able to create the account. + * + * This will be easier when we can more easily create the account and + * *then* switch the site to 1user mode without jumping through hoops. + * + * @return string + * @throws ServerException if no valid single user account is present + * @throws ServerException if called when not in single-user mode + */ + static function singleUserNickname() + { + try { + $user = User::singleUser(); + return $user->nickname; + } catch (Exception $e) { + if (common_config('singleuser', 'enabled') && common_config('singleuser', 'nickname')) { + common_log(LOG_WARNING, "Warning: code attempting to pull single-user nickname when the account does not exist. If this is not setup time, this is probably a bug."); + return common_config('singleuser', 'nickname'); + } + throw $e; + } + } + + /** + * Find and shorten links in the given text using this user's URL shortening + * settings. + * + * By default, links will be left untouched if the text is shorter than the + * configured maximum notice length. Pass true for the $always parameter + * to force all links to be shortened regardless. + * + * Side effects: may save file and file_redirection records for referenced URLs. + * + * @param string $text + * @param boolean $always + * @return string + */ + public function shortenLinks($text, $always=false) + { + return common_shorten_links($text, $always, $this); + } + + /* + * Get a list of OAuth client applications that have access to this + * user's account. + */ + function getConnectedApps($offset = 0, $limit = null) + { + $qry = + 'SELECT u.* ' . + 'FROM oauth_application_user u, oauth_application a ' . + 'WHERE u.profile_id = %d ' . + 'AND a.id = u.application_id ' . + 'AND u.access_type > 0 ' . + 'ORDER BY u.created DESC '; + + if ($offset > 0) { + if (common_config('db','type') == 'pgsql') { + $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset; + } else { + $qry .= ' LIMIT ' . $offset . ', ' . $limit; + } + } + + $apps = new Oauth_application_user(); + + $cnt = $apps->query(sprintf($qry, $this->id)); + + return $apps; + } + + /** + * Magic function called at serialize() time. + * + * We use this to drop a couple process-specific references + * from DB_DataObject which can cause trouble in future + * processes. + * + * @return array of variable names to include in serialization. + */ + + function __sleep() + { + $vars = parent::__sleep(); + $skip = array('_profile'); + return array_diff($vars, $skip); + } + + static function recoverPassword($nore) + { + $user = User::staticGet('email', common_canonical_email($nore)); + + if (!$user) { + try { + $user = User::staticGet('nickname', common_canonical_nickname($nore)); + } catch (NicknameException $e) { + // invalid + } + } + + // See if it's an unconfirmed email address + + if (!$user) { + // Warning: it may actually be legit to have multiple folks + // who have claimed, but not yet confirmed, the same address. + // We'll only send to the first one that comes up. + $confirm_email = new Confirm_address(); + $confirm_email->address = common_canonical_email($nore); + $confirm_email->address_type = 'email'; + $confirm_email->find(); + if ($confirm_email->fetch()) { + $user = User::staticGet($confirm_email->user_id); + } else { + $confirm_email = null; + } + } else { + $confirm_email = null; + } + + if (!$user) { + // TRANS: Information on password recovery form if no known username or e-mail address was specified. + throw new ClientException(_('No user with that email address or username.')); + return; + } + + // Try to get an unconfirmed email address if they used a user name + + if (!$user->email && !$confirm_email) { + $confirm_email = new Confirm_address(); + $confirm_email->user_id = $user->id; + $confirm_email->address_type = 'email'; + $confirm_email->find(); + if (!$confirm_email->fetch()) { + $confirm_email = null; + } + } + + if (!$user->email && !$confirm_email) { + // TRANS: Client error displayed on password recovery form if a user does not have a registered e-mail address. + throw new ClientException(_('No registered email address for that user.')); + return; + } + + // Success! We have a valid user and a confirmed or unconfirmed email address + + $confirm = new Confirm_address(); + $confirm->code = common_confirmation_code(128); + $confirm->address_type = 'recover'; + $confirm->user_id = $user->id; + $confirm->address = (!empty($user->email)) ? $user->email : $confirm_email->address; + + if (!$confirm->insert()) { + common_log_db_error($confirm, 'INSERT', __FILE__); + // TRANS: Server error displayed if e-mail address confirmation fails in the database on the password recovery form. + throw new ServerException(_('Error saving address confirmation.')); + return; + } + + // @todo FIXME: needs i18n. + $body = "Hey, $user->nickname."; + $body .= "\n\n"; + $body .= 'Someone just asked for a new password ' . + 'for this account on ' . common_config('site', 'name') . '.'; + $body .= "\n\n"; + $body .= 'If it was you, and you want to confirm, use the URL below:'; + $body .= "\n\n"; + $body .= "\t".common_local_url('recoverpassword', + array('code' => $confirm->code)); + $body .= "\n\n"; + $body .= 'If not, just ignore this message.'; + $body .= "\n\n"; + $body .= 'Thanks for your time, '; + $body .= "\n"; + $body .= common_config('site', 'name'); + $body .= "\n"; + + $headers = _mail_prepare_headers('recoverpassword', $user->nickname, $user->nickname); + // TRANS: Subject for password recovery e-mail. + mail_to_user($user, _('Password recovery requested'), $body, $headers, $confirm->address); + } + + function streamModeOnly() + { + if (common_config('oldschool', 'enabled')) { + $osp = Old_school_prefs::staticGet('user_id', $this->id); + if (!empty($osp)) { + return $osp->stream_mode_only; + } + } + + return false; + } + + function conversationTree() + { + if (common_config('oldschool', 'enabled')) { + $osp = Old_school_prefs::staticGet('user_id', $this->id); + if (!empty($osp)) { + return $osp->conversation_tree; + } + } + + return false; + } + + function streamNicknames() + { + if (common_config('oldschool', 'enabled')) { + $osp = Old_school_prefs::staticGet('user_id', $this->id); + if (!empty($osp)) { + return $osp->stream_nicknames; + } + } + return false; + } + + function registrationActivity() + { + $profile = $this->getProfile(); + + $service = new ActivityObject(); + + $service->type = ActivityObject::SERVICE; + $service->title = common_config('site', 'name'); + $service->link = common_root_url(); + $service->id = $service->link; + + $act = new Activity(); + + $act->actor = ActivityObject::fromProfile($profile); + $act->verb = ActivityVerb::JOIN; + + $act->objects[] = $service; + + $act->id = TagURI::mint('user:register:%d', + $this->id); + + $act->time = strtotime($this->created); + + $act->title = _("Register"); + + $act->content = sprintf(_('%1$s joined %2$s.'), + $profile->getBestName(), + $service->title); + return $act; + } +} diff --git a/api-changes-1.1.1/lib/apiaction.php b/api-changes-1.1.1/lib/apiaction.php index 06c057f..bf69edb 100644 --- a/api-changes-1.1.1/lib/apiaction.php +++ b/api-changes-1.1.1/lib/apiaction.php @@ -220,11 +220,13 @@ class ApiAction extends Action $twitter_user['profile_image_url_original'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_PROFILE_SIZE); - $twitter_user['groups_count'] = $profile->getGroups(0, null)->N; + $twitter_user['groups_count'] = $profile->getGroups(0, null)->N; + $twitter_user['linkcolor'] = $user->linkcolor; $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null; $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false; $twitter_user['followers_count'] = $profile->subscriberCount(); + // Note: some profiles don't have an associated user diff --git a/api-changes-1.1.1/lib/router.php b/api-changes-1.1.1/lib/router.php index 268f952..1c4cf46 100644 --- a/api-changes-1.1.1/lib/router.php +++ b/api-changes-1.1.1/lib/router.php @@ -483,7 +483,10 @@ class Router $m->connect('api/statusnet/groups/admins/:id.:format', array('action' => 'ApiGroupAdmins', 'id' => Nickname::INPUT_FMT, - 'format' => '(xml|json)')); + 'format' => '(xml|json)')); + + $m->connect('api/account/update_link_color.json', + array('action' => 'ApiAccountUpdateLinkColor')); // users diff --git a/css/1.css b/css/1.css index 912e568..586ea0f 100644 --- a/css/1.css +++ b/css/1.css @@ -47,7 +47,6 @@ body { } a, a:visited, a:active { text-decoration:none; - color:#0084B4; } ul, li { margin:0; @@ -74,6 +73,10 @@ button.icon.nav-search { background-image:url("../img/logo.png"); } +#settingslink { + display:none; + } + .topbar { box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25); left: 0; @@ -346,6 +349,12 @@ button.icon.nav-search span { color: #FFFFFF; text-decoration: none; } +.dropdown-menu li.dropdown-divider { + border-bottom: 1px solid #DDDDDD; + margin: 5px 1px 6px; + padding-top: 1px; + width:123px; + } #birds-top { display:block; @@ -667,7 +676,6 @@ button#submit-login:hover { margin-bottom:10px; } #user-header:hover #user-name { - color: #0084B4; text-decoration:underline; } @@ -692,7 +700,6 @@ button#submit-login:hover { .menu-container div { cursor:pointer; background-color: #FFFFFF; - color: #0084B4; text-decoration: none; padding: 8px 12px; position: relative; @@ -743,6 +750,40 @@ button#submit-login:hover { #history-container { display:none; } + +#settings-container { + padding:10px 10px 300px 10px; + } + +#settings-container label { + float: left; + padding-top: 5px; + text-align: right; + width: 120px; + color: #333333; + cursor: pointer; + display: block; + margin-bottom: 5px; + padding-right:10px; + font-size: 13px; + line-height: 20px; + font-family: "Helvetica Neue",Arial,sans-serif; + } + +#settings-container input { + font-family: "Helvetica Neue",Arial,sans-serif; + font-size: 13px; + line-height: 20px; + background-color: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 3px 3px 3px 3px; + display: inline-block; + outline: 0 none; + height:26px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05) inset, 0 1px 0 rgba(255, 255, 255, 0.075); + transition: background 0.2s linear 0s; + padding-right: 5px; + } #feed { display:none; @@ -849,7 +890,6 @@ button#submit-login:hover { text-shadow: 0 1px 1px rgba(255, 255, 255, 0.6); top: -1px; z-index: 2; - color: #0084B4; } #new-queets-bar:hover { background-color: #eee; @@ -905,7 +945,6 @@ button#submit-login:hover { font-style:italic; } .show-full-conversation:hover { - color:#0084B4; text-decoration:underline; } .queet { @@ -959,7 +998,6 @@ body.rtl .queet:not(.rtl) .stream-item-header { background-color:#F6F6F6; } .queet:hover .stream-item-expand { - color:#0084B4; } .queet:hover .stream-item-expand:hover { text-decoration:underline; @@ -1032,8 +1070,7 @@ body.rtl .view-more-container-bottom { direction:rtl; } } .stream-item-header a.account-group:hover .name { - text-decoration:underline; - color:#0084B4; + text-decoration:underline; } .stream-item-header .avatar { @@ -1101,7 +1138,6 @@ body.rtl .view-more-container-bottom { direction:rtl; } } .stream-item-header .created-at a:hover { text-decoration: underline; - color:#0084B4; } .dogear { @@ -1163,8 +1199,7 @@ body.rtl .view-more-container-bottom { direction:rtl; } margin:0;padding:0; } -.queet-text span.attachment.more { - color:#0084B4; +.queet-text span.attachment.more { } .queet-text span.attachment.more:hover { text-decoration:underline; @@ -1240,8 +1275,7 @@ ul.queet-actions { ul.queet-actions li { display:inline; } -ul.queet-actions li .with-icn { - color: #0084B4; +ul.queet-actions li .with-icn { margin-left: 8px; } ul.queet-actions li .with-icn b { @@ -1253,7 +1287,6 @@ ul.queet-actions li .with-icn b:hover { text-decoration:underline; } ul.queet-actions li .icon { - background-color: #0084B4; background-repeat: no-repeat; display: inline-block; vertical-align: text-top; @@ -1392,7 +1425,6 @@ ul.queet-actions li .icon.sm-fav { } .stream-item-footer .with-icn .requeet-text a b:hover { text-decoration:underline; - color:#0084B4; } .stream-item-expand { @@ -1413,7 +1445,6 @@ ul.queet-actions li .icon.sm-fav { transition: all 0.1s ease; } .stream-item.expanded > .queet .stream-item-expand { - color: #0084B4; } .stream-item.expanded .stream-item-expand:hover { text-decoration:underline; @@ -1564,7 +1595,6 @@ ul.stats .avatar-row .avatar { } .permalink-link:hover { text-decoration:underline; - color:#0084B4; } .inline-reply-queetbox { @@ -1800,7 +1830,6 @@ ul.stats li:hover a, ul.stats li:hover a strong, #user-body a:hover div strong, #user-body a:hover div div { - color:#0084B4; } #user-footer { @@ -2051,23 +2080,23 @@ ul.stats li:hover a strong, background-position:0 0; cursor:pointer; } -#logolink:hover #logo { +#logo:hover { background-position: 0 34px; } -#logolink:hover i.nav-session { +#settingslink:hover i.nav-session { background-position: -160px -80px; } -#logolink:hover .caret { +#settingslink:hover .caret { border-top:4px solid #fff; } -#logolink .dropdown-toggle { +#settingslink .dropdown-toggle { left: 50%; margin-left: -340px; position: fixed; z-index: 1001; } -#logolink .caret { +#settingslink .caret { display:block; position:absolute; margin-left:25px; @@ -2628,6 +2657,15 @@ body.rtl .modal-footer div.right { border-color: #096EB3; color: #FFFFFF; } +.modal-footer button.primary.disabled { + background-color: #DDDDDD; + background-image: none; + border-color: #CCCCCC; + color: #777777; + cursor: default; + opacity: 0.65; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + } body.rtl .modal-footer button { font-family:Tahoma,Arial,sans-serif; margin-left: 10px; @@ -2820,18 +2858,18 @@ body.rtl .dropdown-toggle .caret { margin-left:0; } -body.rtl #logolink .dropdown-toggle { +body.rtl #settingslink .dropdown-toggle { left: auto; right:50%; margin-left:0; margin-right: -343px; direction:rtl; } -body.rtl #logolink .nav-session { +body.rtl #settingslink .nav-session { margin-left:6px; margin-right:0; } -body.rtl #logolink .caret { +body.rtl #settingslink .caret { display:block; position:absolute; margin-left:0; @@ -2920,10 +2958,10 @@ body.rtl #feed-header-inner h2 { body.rtl #logo { margin-right:-265px; } - #logolink .dropdown-toggle { + #settingslink .dropdown-toggle { margin-left: -187px; } - body.rtl #logolink .dropdown-toggle { + body.rtl #settingslink .dropdown-toggle { margin-right: -189px; margin-left:0; } @@ -2949,7 +2987,7 @@ body.rtl #feed-header-inner h2 { #logo{ margin-left: -48.5%; } - #logolink .dropdown-toggle { + #settingslink .dropdown-toggle { left:0; padding-left:5px; margin-left: 95px; @@ -2964,7 +3002,7 @@ body.rtl #feed-header-inner h2 { margin-left:0; margin-right:15px; } - body.rtl #logolink .dropdown-toggle { + body.rtl #settingslink .dropdown-toggle { left:auto; right:0; padding-left:0; @@ -3023,10 +3061,10 @@ body.rtl #feed-header-inner h2 { #logo { display:none; } - #logolink .dropdown-toggle { + #settingslink .dropdown-toggle { margin-left:5px; } - body.rtl #logolink .dropdown-toggle { + body.rtl #settingslink .dropdown-toggle { margin-right:5px; } diff --git a/css/jquery.minicolors.css b/css/jquery.minicolors.css new file mode 100755 index 0000000..8dc0448 --- /dev/null +++ b/css/jquery.minicolors.css @@ -0,0 +1,245 @@ +.minicolors { + position: relative; +} + +.minicolors-swatch { + position: absolute; + vertical-align: middle; + background: url(../img/jquery.minicolors.png) -80px 0; + border: solid 1px #ccc; + cursor: text; + padding: 0; + margin: 0; + display: inline-block; +} + +.minicolors-swatch-color { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.minicolors input[type=hidden] + .minicolors-swatch { + width: 28px; + position: static; + cursor: pointer; +} + +/* Panel */ +.minicolors-panel { + position: absolute; + width: 173px; + height: 152px; + background: white; + border: solid 1px #CCC; + box-shadow: 0 0 20px rgba(0, 0, 0, .2); + z-index: 99999; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; + display: none; +} + +.minicolors-panel.minicolors-visible { + display: block; +} + +/* Panel positioning */ +.minicolors-position-top .minicolors-panel { + top: -154px; +} + +.minicolors-position-right .minicolors-panel { + right: 0; +} + +.minicolors-position-bottom .minicolors-panel { + top: auto; +} + +.minicolors-position-left .minicolors-panel { + left: 0; +} + +.minicolors-with-opacity .minicolors-panel { + width: 194px; +} + +.minicolors .minicolors-grid { + position: absolute; + top: 1px; + left: 1px; + width: 150px; + height: 150px; + background: url(../img/jquery.minicolors.png) -120px 0; + cursor: crosshair; +} + +.minicolors .minicolors-grid-inner { + position: absolute; + top: 0; + left: 0; + width: 150px; + height: 150px; + background: none; +} + +.minicolors-slider-saturation .minicolors-grid { + background-position: -420px 0; +} + +.minicolors-slider-saturation .minicolors-grid-inner { + background: url(../img/jquery.minicolors.png) -270px 0; +} + +.minicolors-slider-brightness .minicolors-grid { + background-position: -570px 0; +} + +.minicolors-slider-brightness .minicolors-grid-inner { + background: black; +} + +.minicolors-slider-wheel .minicolors-grid { + background-position: -720px 0; +} + +.minicolors-slider, +.minicolors-opacity-slider { + position: absolute; + top: 1px; + left: 152px; + width: 20px; + height: 150px; + background: white url(../img/jquery.minicolors.png) 0 0; + cursor: row-resize; +} + +.minicolors-slider-saturation .minicolors-slider { + background-position: -60px 0; +} + +.minicolors-slider-brightness .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-slider-wheel .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-opacity-slider { + left: 173px; + background-position: -40px 0; + display: none; +} + +.minicolors-with-opacity .minicolors-opacity-slider { + display: block; +} + +/* Pickers */ +.minicolors-grid .minicolors-picker { + position: absolute; + top: 70px; + left: 70px; + width: 12px; + height: 12px; + border: solid 1px black; + border-radius: 10px; + margin-top: -6px; + margin-left: -6px; + background: none; +} + +.minicolors-grid .minicolors-picker > div { + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 8px; + border-radius: 8px; + border: solid 2px white; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +.minicolors-picker { + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 2px; + background: white; + border: solid 1px black; + margin-top: -2px; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} + +/* Inline controls */ +.minicolors-inline { + display: inline-block; +} + +.minicolors-inline .minicolors-input { + display: none !important; +} + +.minicolors-inline .minicolors-panel { + position: relative; + top: auto; + left: auto; + box-shadow: none; + z-index: auto; + display: inline-block; +} + +/* Default theme */ +.minicolors-theme-default .minicolors-swatch { + top: 5px; + left: 5px; + width: 18px; + height: 18px; +} +.minicolors-theme-default.minicolors-position-right .minicolors-swatch { + left: auto; + right: 5px; +} +.minicolors-theme-default.minicolors { + width: auto; + display: inline-block; +} +.minicolors-theme-default .minicolors-input { + height: 20px; + width: auto; + display: inline-block; + padding-left: 26px; +} +.minicolors-theme-default.minicolors-position-right .minicolors-input { + padding-right: 26px; + padding-left: inherit; +} + +/* Bootstrap theme */ +.minicolors-theme-bootstrap .minicolors-swatch { + top: 3px; + left: 3px; + width: 28px; + height: 28px; + border-radius: 3px; +} +.minicolors-theme-bootstrap.minicolors-position-right .minicolors-swatch { + left: auto; + right: 3px; +} +.minicolors-theme-bootstrap .minicolors-input { + padding-left: 44px; +} +.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input { + padding-right: 44px; + padding-left: 12px; +} \ No newline at end of file diff --git a/img/jquery.minicolors.png b/img/jquery.minicolors.png new file mode 100755 index 0000000..8fa1e9d Binary files /dev/null and b/img/jquery.minicolors.png differ diff --git a/index.php b/index.php index 8737892..f3aa123 100644 --- a/index.php +++ b/index.php @@ -39,6 +39,7 @@ + +
- - + "> +