Merge branch 'nightly'
Conflicts: INSTALL
							
								
								
									
										34
									
								
								EVENTS.txt
									
									
									
									
									
								
							
							
						
						| @@ -2,6 +2,13 @@ InitializePlugin: a chance to initialize a plugin in a complete environment | ||||
|  | ||||
| CleanupPlugin: a chance to cleanup a plugin at the end of a program | ||||
|  | ||||
| StartActionExecute: Right before the "prepare" call of the current Action | ||||
| - $action:  the current Action object | ||||
| - &$args:   array of arguments, referenced so you can modify the array | ||||
|  | ||||
| EndActionExecute:   Right after the "handle" call of the current Action | ||||
| - $action:      the current Action object | ||||
|  | ||||
| StartPrimaryNav: Showing the primary nav menu | ||||
| - $action: the current action | ||||
|  | ||||
| @@ -1444,6 +1451,9 @@ StartResizeImageFile: Hook to resize an image and output it to a file. No matchi | ||||
| - $outpath:   string with output filepath | ||||
| - $box:       array with size ('width', 'height') and boundary box('x', 'y', 'w', 'h'). | ||||
|  | ||||
| FillImageFileMetadata: Get more metadata about the ImageFile if it is perhaps not a real local file | ||||
| - $imagefile    ImageFile object which we're getting metadata for (such as animated status, width/height etc.) | ||||
|  | ||||
| StartShowAttachmentRepresentation: Attachment representation, full file (or in rare cases thumbnails/previews). | ||||
| - $out:     HTMLOutputter class to use for outputting HTML. | ||||
| - $file:    'File' object which we're going to show representation for. | ||||
| @@ -1463,3 +1473,27 @@ StartNotifyMentioned: During notice distribution, we send notifications (email, | ||||
| EndNotifyMentioned: During notice distribution, we send notifications (email, im...) to the profiles who were somehow mentioned. | ||||
| - $stored:         Notice object that is being distributed. | ||||
| - $mentioned_ids:  Array of profile IDs (not just for local users) who got mentioned by the notice. | ||||
|  | ||||
| StartHomeStubNavItems: Go back Home nav items. Default includes just one item 'home' | ||||
| - $out:     HTMLOutputter used to output (usually an Action, but not always!) | ||||
| - &$items:  Referenced array of items in the nav (add if desired) | ||||
|  | ||||
| EndHomeStubNavItems: | ||||
| - $out:     HTMLOutputter used to output (usually an Action, but not always!) | ||||
| - $items:   array of menu items | ||||
|  | ||||
| StartSubMenu: Before outputting a submenu (including enclosing tags) to HTML | ||||
| - $out:     HTMLOutputter used to output (usually an Action, but not always!) | ||||
| - $menu:    The Menu object outputted as a submenu. | ||||
| - $label:   Localized text which represents the menu item. | ||||
|  | ||||
| EndSubMenu: After outputting a submenu (including enclosing tags) to HTML | ||||
| - $out:     HTMLOutputter used to output (usually an Action, but not always!) | ||||
| - $menu:    The Menu object outputted as a submenu. | ||||
| - $label:   Localized text which represents the menu item. | ||||
|  | ||||
| StartDocNav: Before outputting the docs Nav | ||||
| - $nav: The DoclNav widget | ||||
|  | ||||
| EndDocNav: After outputting the docs Nav | ||||
| - $nav: The DoclNav widget | ||||
|   | ||||
							
								
								
									
										238
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						| @@ -6,13 +6,16 @@ TABLE OF CONTENTS | ||||
| * Installation | ||||
|     - Getting it up and running | ||||
|     - Fancy URLs | ||||
|     - Themes | ||||
|     - Private | ||||
| * Extra features | ||||
|     - Sphinx | ||||
|     - SMS | ||||
|     - Queues and daemons | ||||
|     - Themes | ||||
|     - Translation | ||||
|     - Queues and daemons | ||||
| * After installation | ||||
|     - Backups | ||||
|     - Private | ||||
|     - Upgrading | ||||
|  | ||||
| Prerequisites | ||||
| ============= | ||||
| @@ -41,10 +44,10 @@ functional setup of GNU Social: | ||||
| - php5-curl     Fetching files by HTTP. | ||||
| - php5-gd       Image manipulation (scaling). | ||||
| - php5-gmp      For Salmon signatures (part of OStatus). | ||||
| - php5-intl     Internationalization support, part of core. | ||||
| - php5-intl     Internationalization support (transliteration et al). | ||||
| - php5-json     For WebFinger lookups and more. | ||||
| - php5-mysqlnd  The native driver for PHP5 MariaDB connections. If you | ||||
|                   use MySQL, 'mysql' or 'mysqli' may work. | ||||
|                   use MySQL, 'php5-mysql' or 'php5-mysqli' may be enough. | ||||
|  | ||||
| The above package names are for Debian based systems. In the case of | ||||
| Arch Linux, PHP is compiled with support for most extensions but they | ||||
| @@ -69,7 +72,7 @@ For some functionality, you will also need the following extensions: | ||||
|  | ||||
| You may also experience better performance from your site if you configure | ||||
| a PHP cache/accelerator. Most distributions come with "opcache" support. | ||||
| Enable it in your php.ini, it is documented there together with its settings. | ||||
| Enable it in your php.ini where it is documented together with its settings. | ||||
|  | ||||
| Installation | ||||
| ============ | ||||
| @@ -131,9 +134,9 @@ especially if you've previously installed PHP/MariaDB packages. | ||||
|    writeable by the Web server group, as noted above. | ||||
|  | ||||
| 5. Create a database to hold your site data. Something like this | ||||
|    should work: | ||||
|    should work (you will be prompted for your database password): | ||||
|  | ||||
|        mysqladmin -u "root" --password="rootpassword" create gnusocial | ||||
|        mysqladmin -u "root" -p create social | ||||
|  | ||||
|    Note that GNU Social should have its own database; you should not share | ||||
|    the database with another program. You can name it whatever you want, | ||||
| @@ -147,17 +150,17 @@ especially if you've previously installed PHP/MariaDB packages. | ||||
|    database. If you have shell access, this will probably work from the | ||||
|    MariaDB shell: | ||||
|  | ||||
|        GRANT ALL on gnusocial.* | ||||
|        TO 'gnusocial'@'localhost' | ||||
|        GRANT ALL on social.* | ||||
|        TO 'social'@'localhost' | ||||
|        IDENTIFIED BY 'agoodpassword'; | ||||
|  | ||||
|    You should change the user identifier 'gnusocial' and 'agoodpassword' | ||||
|    You should change the user identifier 'social' and 'agoodpassword' | ||||
|    to your preferred new database username and password. You may want to | ||||
|    test logging in to MariaDB as this new user. | ||||
|  | ||||
| 7. In a browser, navigate to the GNU Social install script; something like: | ||||
|  | ||||
|        http://social.example.net/install.php | ||||
|        https://social.example.net/install.php | ||||
|  | ||||
|    Enter the database connection information and your site name. The | ||||
|    install program will configure your site and install the initial, | ||||
| @@ -171,55 +174,100 @@ Fancy URLs | ||||
| ---------- | ||||
|  | ||||
| By default, GNU Social will use URLs that include the main PHP program's | ||||
| name in them. For example, a user's home profile might be found at: | ||||
| name in them. For example, a user's home profile might be found at either | ||||
| of these URLS depending on the webserver's configuration and capabilities: | ||||
|  | ||||
|     http://example.net/gnusocial/index.php/gnusocial/fred | ||||
|     https://social.example.net/index.php/fred | ||||
|     https://social.example.net/index.php?p=fred | ||||
|  | ||||
| On certain systems that don't support this kind of syntax, they'll | ||||
| look like this: | ||||
| It's possible to configure the software to use fancy URLs so it looks like | ||||
| this instead: | ||||
|  | ||||
|     http://example.net/gnusocial/index.php?p=gnusocial/fred | ||||
|  | ||||
| It's possible to configure the software so it looks like this instead: | ||||
|  | ||||
|     http://example.net/gnusocial/fred | ||||
|     https://social.example.net/fred | ||||
|  | ||||
| These "fancy URLs" are more readable and memorable for users. To use | ||||
| fancy URLs, you must either have Apache 2.x with .htaccess enabled and | ||||
| mod_rewrite enabled, -OR- know how to configure "url redirection" in | ||||
| your server (like lighttpd or nginx). | ||||
|  | ||||
| 1. Copy the htaccess.sample file to .htaccess in your StatusNet | ||||
|    directory. | ||||
|  | ||||
| 2. Change the "RewriteBase" in the new .htaccess file to be the URL path | ||||
|    to your GNU Social installation on your server. Typically this will | ||||
|    be the path to your GNU Social directory relative to your Web root. | ||||
|    If you are installing it in the root directory, leave it as '/'. | ||||
|  | ||||
| 3. Add, uncomment or change a line in your config.php file so it says: | ||||
| 1. See the instructions for each respective webserver software: | ||||
|     * For Apache, inspect the "htaccess.sample" file and save it as | ||||
|         ".htaccess" after making any necessary modifications. Our sample | ||||
|         file is well commented.  | ||||
|     * For lighttpd, inspect the lighttpd.conf.example file and apply the | ||||
|         appropriate changes in your virtualhost configuration for lighttpd. | ||||
|     * For nginx and other webservers, we gladly accept contributions of | ||||
|         server configuration examples. | ||||
|  | ||||
| 2. Assuming your webserver is properly configured and have its settings | ||||
|     applied (remember to reload/restart it), you can add this to your | ||||
|     GNU social's config.php file:  | ||||
|        $config['site']['fancy'] = true; | ||||
|  | ||||
| You should now be able to navigate to a "fancy" URL on your server, | ||||
| like: | ||||
|  | ||||
|     http://example.net/gnusocial/main/register | ||||
|     https://social.example.net/main/register | ||||
|  | ||||
| If you changed your HTTP server configuration, you may need to restart | ||||
| the server first. | ||||
| Themes | ||||
| ------ | ||||
|  | ||||
| If it doesn't work, double-check that AllowOverride for the GNU Social | ||||
| directory is 'All' in your Apache configuration file. This is usually | ||||
| /etc/httpd.conf, /etc/apache/httpd.conf, or (on Debian and Ubuntu) | ||||
| /etc/apache2/sites-available/default. See the Apache documentation for | ||||
| .htaccess files for more details: | ||||
| As of right now, your ability change the theme is limited to CSS | ||||
| stylesheets and some image files; you can't change the HTML output, | ||||
| like adding or removing menu items, without the help of a plugin. | ||||
|  | ||||
|     http://httpd.apache.org/docs/2.2/howto/htaccess.html | ||||
| You can choose a theme using the $config['site']['theme'] element in | ||||
| the config.php file. See below for details. | ||||
|  | ||||
| Also, check that mod_rewrite is installed and enabled: | ||||
| You can add your own theme by making a sub-directory of the 'theme' | ||||
| subdirectory with the name of your theme. Each theme can have the | ||||
| following files: | ||||
|  | ||||
|     http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html | ||||
| display.css: a CSS2 file for "default" styling for all browsers. | ||||
| logo.png: a logo image for the site. | ||||
| default-avatar-profile.png: a 96x96 pixel image to use as the avatar for | ||||
|     users who don't upload their own. | ||||
| default-avatar-stream.png: Ditto, but 48x48. For streams of notices. | ||||
| default-avatar-mini.png: Ditto ditto, but 24x24. For subscriptions | ||||
|     listing on profile pages. | ||||
|  | ||||
| You may want to start by copying the files from the default theme to | ||||
| your own directory. | ||||
|  | ||||
| Private | ||||
| ------- | ||||
|  | ||||
| A GNU social node can be configured as "private", which means it will not | ||||
| federate with other nodes in the network. It is not a recommended method | ||||
| of using GNU social and we cannot at the current state of development | ||||
| guarantee that there are no leaks (what a public network sees as features, | ||||
| private sites will likely see as bugs).  | ||||
|  | ||||
| Private nodes are however an easy way to easily setup collaboration and | ||||
| image sharing within a workgroup or a smaller community where federation | ||||
| is not a desired feature. Also, it is possible to change this setting and | ||||
| instantly gain full federation features. | ||||
|  | ||||
| Access to file attachments can also be restricted to logged-in users only: | ||||
|  | ||||
| 1. Add a directory outside the web root where your file uploads will be | ||||
|    stored. Use this command as an initial guideline to create it: | ||||
|  | ||||
|        mkdir /var/www/gnusocial-files | ||||
|  | ||||
| 2. Make the file uploads directory writeable by the web server. An | ||||
|    insecure way to do this is (to do it properly, read up on UNIX file | ||||
|    permissions and configure your webserver accordingly): | ||||
|  | ||||
|        chmod a+x /var/www/gnusocial-files | ||||
|  | ||||
| 3. Tell GNU social to use this directory for file uploads. Add a line | ||||
|    like this to your config.php: | ||||
|  | ||||
|        $config['attachments']['dir'] = '/var/www/gnusocial-files'; | ||||
|  | ||||
| Extra features | ||||
| ============== | ||||
|  | ||||
| Sphinx | ||||
| ------ | ||||
| @@ -284,7 +332,21 @@ For this to work, there *must* be a domain or sub-domain for which all | ||||
|  | ||||
|        $config['mail']['domain'] = 'yourdomain.example.net'; | ||||
|  | ||||
| Translations | ||||
| ------------ | ||||
|  | ||||
| For info on helping with translations, see the platform currently in use | ||||
| for translations: https://www.transifex.com/projects/p/gnu-social/ | ||||
|  | ||||
| Translations use the gettext system <http://www.gnu.org/software/gettext/>. | ||||
| If you for some reason do not wish to sign up to the Transifex service, | ||||
| you can review the files in the "locale/" sub-directory of GNU social. | ||||
| Each plugin also has its own translation files. | ||||
|  | ||||
| To get your own site to use all the translated languages, and you are | ||||
| tracking the git repo, you will need to install at least 'gettext' on | ||||
| your system and then run: | ||||
|     $ make translations | ||||
|  | ||||
| Queues and daemons | ||||
| ------------------ | ||||
| @@ -346,16 +408,13 @@ separate server is probably a good idea for high-volume sites. | ||||
|    .htaccess file, but make sure that your config.php file is close | ||||
|    to, or identical to, your Web server's version. | ||||
|  | ||||
| 3. In your config.php files (both the Web server and the queues | ||||
|    server!), set the following variable: | ||||
| 3. In your config.php files (on the server where you run the queue | ||||
|     daemon), set the following variable: | ||||
|  | ||||
|        $config['queue']['enabled'] = true; | ||||
|        $config['queue']['daemon'] = true; | ||||
|  | ||||
|    You may also want to look at the 'daemon' section of this file for | ||||
|    more daemon options. Note that if you set the 'user' and/or 'group' | ||||
|    options, you'll need to create that user and/or group by hand. | ||||
|    They're not created automatically. | ||||
|    You may also want to look at the 'Queues and Daemons' section in | ||||
|    this file for more background processing options. | ||||
|  | ||||
| 4. On the queues server, run the command scripts/startdaemons.sh. | ||||
|  | ||||
| @@ -385,85 +444,20 @@ It is also possible to use a STOMP server instead of our kind of hacky | ||||
| home-grown DB-based queue solution. This is strongly recommended for | ||||
| best response time, especially when using XMPP. | ||||
|  | ||||
| Themes | ||||
| ------ | ||||
|  | ||||
| Older themes (version 0.9.x and below) no longer work with StatusNet | ||||
| 1.0.x, due to major changes in the site layout. We ship with three new | ||||
| themes for this version, 'neo', 'neo-blue' and 'neo-light'. | ||||
|  | ||||
| As of right now, your ability to change the theme is site-wide; users | ||||
| can't choose their own theme. Additionally, the only thing you can | ||||
| change in the theme is CSS stylesheets and some image files; you can't | ||||
| change the HTML output, like adding or removing menu items. | ||||
|  | ||||
| You can choose a theme using the $config['site']['theme'] element in | ||||
| the config.php file. See below for details. | ||||
|  | ||||
| You can add your own theme by making a sub-directory of the 'theme' | ||||
| subdirectory with the name of your theme. Each theme can have the | ||||
| following files: | ||||
|  | ||||
| display.css: a CSS2 file for "default" styling for all browsers. | ||||
| logo.png: a logo image for the site. | ||||
| default-avatar-profile.png: a 96x96 pixel image to use as the avatar for | ||||
|     users who don't upload their own. | ||||
| default-avatar-stream.png: Ditto, but 48x48. For streams of notices. | ||||
| default-avatar-mini.png: Ditto ditto, but 24x24. For subscriptions | ||||
|     listing on profile pages. | ||||
|  | ||||
| You may want to start by copying the files from the default theme to | ||||
| your own directory. | ||||
|  | ||||
| Translation | ||||
| ----------- | ||||
|  | ||||
| Translations in StatusNet use the gettext system <http://www.gnu.org/software/gettext/>. | ||||
| Theoretically, you can add your own sub-directory to the locale/ | ||||
| subdirectory to add a new language to your system. You'll need to | ||||
| compile the ".po" files into ".mo" files, however. | ||||
|  | ||||
| Contributions of translation information to StatusNet are very easy: | ||||
| you can use the Web interface at translatewiki.net to add one | ||||
| or a few or lots of new translations -- or even new languages. You can | ||||
| also download more up-to-date .po files there, if you so desire. | ||||
|  | ||||
| For info on helping with translations, see http://status.net/wiki/Translations | ||||
| After installation | ||||
| ================== | ||||
|  | ||||
| Backups | ||||
| ------- | ||||
|  | ||||
| There is no built-in system for doing backups in StatusNet. You can make | ||||
| There is no built-in system for doing backups in GNU social. You can make | ||||
| backups of a working StatusNet system by backing up the database and | ||||
| the Web directory. To backup the database use mysqldump <http://ur1.ca/7xo> | ||||
| the Web directory. To backup the database use mysqldump <https://mariadb.com/kb/en/mariadb/mysqldump/> | ||||
| and to backup the Web directory, try tar. | ||||
|  | ||||
| Private | ||||
| ------- | ||||
| Upgrading | ||||
| --------- | ||||
|  | ||||
| The administrator can set the "private" flag for a site so that it's | ||||
| not visible to non-logged-in users. (This is the default for new installs of version 1.0!) | ||||
|  | ||||
| This might be useful for workgroups who want to share a social | ||||
| networking site for project management, but host it on a public | ||||
| server. | ||||
|  | ||||
| Total privacy is attempted but not guaranteed or ensured. Private sites | ||||
| currently don't work well with OStatus federation. | ||||
|  | ||||
| Access to file attachments can also be restricted to logged-in users only. | ||||
|  | ||||
| 1. Add a directory outside the web root where your file uploads will be | ||||
|    stored. Usually a command like this will work: | ||||
|  | ||||
|        mkdir /var/www/statusnet-files | ||||
|  | ||||
| 2. Make the file uploads directory writeable by the web server. An | ||||
|    insecure way to do this is: | ||||
|  | ||||
|        chmod a+x /var/www/statusnet-files | ||||
|  | ||||
| 3. Tell StatusNet to use this directory for file uploads. Add a line | ||||
|    like this to your config.php: | ||||
|  | ||||
|        $config['attachments']['dir'] = '/var/www/statusnet-files'; | ||||
| Upgrading is strongly recommended to stay up to date with security fixes | ||||
| and new features. For instructions on how to upgrade GNU social code, | ||||
| please see the UPGRADE file. | ||||
|   | ||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| # GNU social 1.1.3 | ||||
| February 2015-02-27 | ||||
| # GNU social 1.2.x | ||||
| 2015 | ||||
|  | ||||
| (c) Free Software Foundation, Inc | ||||
| (c) StatusNet, Inc | ||||
| @@ -100,15 +100,19 @@ for additional terms. | ||||
|  | ||||
| ## New this version | ||||
|  | ||||
| This is a security fix and bug fix release since 1.1.3-beta2. | ||||
| All 1.1.x sites should upgrade to this version. | ||||
| This is the development branch for the 1.2.x version of GNU social. | ||||
| All daring 1.1.x admins should upgrade to this version. | ||||
|  | ||||
| So far it includes the following changes: | ||||
|  | ||||
| - Backing up a user's account is more and more complete. | ||||
| - Emojis 😸 (utf8mb4 support) | ||||
|  | ||||
| The last release, 1.1.3, gave us these improvements: | ||||
|  | ||||
| - XSS security fix (thanks Simon Waters, <https://www.surevine.com/>) | ||||
| - Many improvements to ease adoption of the Qvitter front-end <https://github.com/hannesmannerheim/qvitter> | ||||
| - Protocol adaptions for improved performance and stability | ||||
| - Backing up a user's account now appears to work as it should | ||||
|  | ||||
| Upgrades from _StatusNet_ 1.1.1 will also experience these improvements: | ||||
|  | ||||
| @@ -146,7 +150,7 @@ In the current phase of development it is probably | ||||
| recommended to use git as a means to stay up to date | ||||
| with the source code. You can choose between these | ||||
| branches: | ||||
| - 1.1.x     "stable", few updates, well tested code | ||||
| - 1.2.x     "stable", few updates, well tested code | ||||
| - master    "testing", more updates, usually working well | ||||
| - nightly   "unstable", most updates, not always working | ||||
|  | ||||
|   | ||||
							
								
								
									
										157
									
								
								UPGRADE
									
									
									
									
									
								
							
							
						
						| @@ -1,99 +1,98 @@ | ||||
| Upgrading | ||||
| ========= | ||||
|  | ||||
| StatusNet 1.1.1 to GNU social | ||||
| ----------------------------- | ||||
| GNU social 1.1.x to GNU social 1.2.x | ||||
| ------------------------------------ | ||||
|  | ||||
| If you are tracking the GNU social git repository, we currently recommend | ||||
| using the "master" branch (or nightly if you want to use latest features) | ||||
| and follow this procedure:  | ||||
|  | ||||
| 0. Backup your data. The StatusNet upgrade discussions below have some | ||||
|     guidelines to back up the database and files (mysqldump and rsync). | ||||
|  | ||||
| 1. Stop your queue daemons (you can run this command even if you do not | ||||
|     use the queue daemons): | ||||
|     $ bash scripts/stopdaemons.sh | ||||
|  | ||||
| 2. Run the command to fetch the latest sourcecode: | ||||
|     $ git pull | ||||
|      | ||||
|     If you are not using git we recommend following the instructions below | ||||
|     for upgrading "StatusNet 1.1.x to GNU social 1.2.x" as they are similar. | ||||
|  | ||||
| 3. Run the upgrade script: | ||||
|     $ php scripts/upgrade.php | ||||
|  | ||||
|    The upgrade script will likely take a long time because it will | ||||
|     upgrade the tables to another character encoding and make other | ||||
|     automated upgrades. Make sure it ends without errors. If you get | ||||
|     errors, create a new task on https://bugz.foocorp.net/ | ||||
|  | ||||
| 4. Start your queue daemons again (you can run this command even if you | ||||
|     do not use the queue daemons): | ||||
|     $ bash scripts/startdaemons.sh | ||||
|  | ||||
| 5. Report any issues at https://bugz.foocorp.net/ (tag GNU social) | ||||
|  | ||||
| If you are using ssh keys to log in to your server, you can make this | ||||
| procedure pretty painless (assuming you have automated backups already). | ||||
| Make sure you "cd" into the correct directory (in this case "htdocs") | ||||
| and use the correct login@hostname combo: | ||||
|     $ ssh social@domain.example 'cd htdocs | ||||
|             && bash scripts/stopdaemons.sh | ||||
|             && git pull | ||||
|             && time php scripts/upgrade.php | ||||
|             && bash scripts/startdaemons.sh' | ||||
|  | ||||
| StatusNet 1.1.x to GNU social 1.2.x | ||||
| ----------------------------------- | ||||
|  | ||||
| We cannot support migrating from any other version of StatusNet than  | ||||
| 1.1.1. If you are running a StatusNet version lower than this, please  | ||||
| follow the upgrade procedures for each respective StatusNet version. | ||||
|  | ||||
| You are now running StatusNet 1.1.1 and want to migrate to GNU social. | ||||
| Beware there may be changes in minimum required version of PHP and the | ||||
| modules used, so double-check the INSTALL file's requirements list. | ||||
| You are now running StatusNet 1.1.1 and want to migrate to GNU social | ||||
| 1.2.x. Beware there may be changes in minimum required version of PHP | ||||
| and the modules required, so review the INSTALL file (php5-intl is a | ||||
| newly added dependency for example). | ||||
|  | ||||
| Before you begin: Make backups. Always make backups. Of your entire  | ||||
| * Before you begin: Make backups. Always make backups. Of your entire  | ||||
| directory structure and the database too. All tables. All data. Alles. | ||||
|  | ||||
|     0. Stop your queue daemons 'php scripts/stopdaemon.php' should do it. | ||||
|         Not everyone runs queue daemons, but the above command won't hurt. | ||||
| 0. Make a backup of everything. To backup the database, you can use a | ||||
| variant of this command (you will be prompted for the database password): | ||||
|     $ mysqldump -u dbuser -p dbname > social-backup.sql | ||||
|  | ||||
|     1. Unpack your GNU social code to a fresh directory. | ||||
|      | ||||
|     2. Synchronize your local files to the GNU social directory. These  | ||||
|         will be the local files such as avatars, config and files: | ||||
| 1. Stop your queue daemons 'bash scripts/stopdaemons.sh' should do it. | ||||
|     Not everyone runs queue daemons, but the above command won't hurt. | ||||
|  | ||||
|             avatar/* | ||||
|             background/* | ||||
|             file/* | ||||
|             local/* | ||||
|             .htaccess | ||||
|             config.php | ||||
| 2. Unpack your GNU social code to a fresh directory. You can do this | ||||
|     by cloning our git repository: | ||||
|     $ git clone https://gitorious.org/social/mainline.git gnusocial | ||||
|  | ||||
|     3. Replace your old StatusNet directory with the new GNU social | ||||
|         directory in your webserver root. | ||||
|      | ||||
|     4. Run the upgrade script: 'php scripts/upgrade.php' | ||||
|      | ||||
|     5. Start your queue daemons: 'php scripts/startdaemons.php' | ||||
|      | ||||
|     6. Report any issues at https://bugz.foocorp.net/ (tag GNU social) | ||||
| 3. Synchronize your local files to the GNU social directory. These  | ||||
|     will be the local files such as avatars, config and files: | ||||
|  | ||||
|         avatar/* | ||||
|         background/* | ||||
|         file/* | ||||
|         local/* | ||||
|         .htaccess | ||||
|         config.php | ||||
|  | ||||
| Legacy StatusNet instructions | ||||
| ----------------------------- | ||||
|     This command will point you in the right direction on how to do it: | ||||
|     $ rsync -avP statusnet/{.htaccess,avatar,background,file,local,config.php} gnusocial/ | ||||
|  | ||||
| These instructions are here for historical and perhaps informational | ||||
| purposes. | ||||
| 4. Replace your old StatusNet directory with the new GNU social | ||||
|     directory in your webserver root. | ||||
|  | ||||
| If you've been using StatusNet 1.0 or lower, or if you've | ||||
| been tracking the "git" version of the software, you will probably | ||||
| want to upgrade and keep your existing data. Try these step-by-step | ||||
| instructions; read to the end first before trying them. | ||||
| 5. Run the upgrade script: 'php scripts/upgrade.php' | ||||
|    The upgrade script will likely take a long time because it will | ||||
|     upgrade the tables to another character encoding and make other | ||||
|     automated upgrades. Make sure it ends without errors. If you get | ||||
|     errors, create a new task on https://bugz.foocorp.net/ | ||||
|  | ||||
| 0. Download StatusNet and set up all the prerequisites as if you were | ||||
|    doing a new install. | ||||
| 1. Make backups of both your database and your Web directory. UNDER NO | ||||
|    CIRCUMSTANCES should you try to do an upgrade without a known-good | ||||
|    backup. You have been warned. | ||||
| 2. Shut down Web access to your site, either by turning off your Web | ||||
|    server or by redirecting all pages to a "sorry, under maintenance" | ||||
|    page. | ||||
| 3. Shut down XMPP access to your site, typically by shutting down the | ||||
|    xmppdaemon.php process and all other daemons that you're running. | ||||
|    If you've got "monit" or "cron" automatically restarting your | ||||
|    daemons, make sure to turn that off, too. | ||||
| 4. Shut down SMS and email access to your site. The easy way to do | ||||
|    this is to comment out the line piping incoming email to your | ||||
|    maildaemon.php file, and running something like "newaliases". | ||||
| 5. Once all writing processes to your site are turned off, make a | ||||
|    final backup of the Web directory and database. | ||||
| 6. Move your StatusNet directory to a backup spot, like "statusnet.bak". | ||||
| 7. Unpack your StatusNet 1.1.1 tarball and move it to "statusnet" or | ||||
|    wherever your code used to be. | ||||
| 8. Copy the config.php file and the contents of the avatar/, background/, | ||||
|    file/, and local/ subdirectories from your old directory to your new | ||||
|    directory. | ||||
| 9. Copy htaccess.sample to .htaccess in the new directory. Change the | ||||
|    RewriteBase to use the correct path. | ||||
| 10. Upgrade the database. | ||||
| 6. Start your queue daemons: 'bash scripts/startdaemons.sh' | ||||
|  | ||||
|     NOTE: this step is destructive and cannot be | ||||
|     reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't | ||||
|     do it without a known-good backup! | ||||
|  | ||||
|     In your new StatusNet 1.1.1 directory and AFTER YOU MAKE A | ||||
|     BACKUP run the upgrade.php script like this: | ||||
|  | ||||
|         php ./scripts/upgrade.php | ||||
|  | ||||
| 11. Use mysql or psql client to log into your database and make sure that | ||||
|     the notice, user, profile, subscription etc. tables are non-empty. | ||||
| 12. Turn back on the Web server, and check that things still work. | ||||
| 13. Turn back on XMPP bots and email maildaemon. | ||||
|  | ||||
| NOTE: the 1.0.0 version of StatusNet changed the URLs for all admin | ||||
| panels from /admin/* to /panel/*. This now allows the (popular) | ||||
| username 'admin', but blocks the considerably less popular username | ||||
| 'panel'. If you have an existing user named 'panel', you should rename | ||||
| them before upgrading. | ||||
| 7. Report any issues at https://bugz.foocorp.net/ (tag GNU social) | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| <?php | ||||
|          | ||||
| /** | ||||
|  * StatusNet, the distributed open-source microblogging tool | ||||
|  * | ||||
| @@ -152,34 +151,29 @@ class ApiAccountRegisterAction extends ApiAction | ||||
|             // TRANS: Form validation error displayed when trying to register with non-matching passwords. | ||||
| 	        $this->clientError(_('Passwords do not match.'), 400); | ||||
|         } else { | ||||
| 	     | ||||
| 	    	// annoy spammers | ||||
| 	    	sleep(7); | ||||
| 	     | ||||
| 	    	if ($user = User::register(array('nickname' => $nickname, | ||||
| 	                                                'password' => $password, | ||||
| 	                                                'email' => $email, | ||||
| 	                                                'fullname' => $fullname, | ||||
| 	                                                'homepage' => $homepage, | ||||
| 	                                                'bio' => $bio, | ||||
| 	                                                'location' => $location, | ||||
| 	                                                'code' => $this->code))) { | ||||
| 	            if (!$user instanceof User) { | ||||
| 	                // TRANS: Form validation error displayed when trying to register with an invalid username or password. | ||||
| 	                $this->clientError(_('Invalid username or password.'), 400); | ||||
| 	            } | ||||
|  | ||||
| 	            Event::handle('EndRegistrationTry', array($this)); | ||||
|             // annoy spammers | ||||
|             sleep(7); | ||||
|  | ||||
| 	            $this->initDocument('json'); | ||||
| 	            $this->showJsonObjects($this->twitterUserArray($user->getProfile())); | ||||
| 	            $this->endDocument('json'); | ||||
|             try { | ||||
|                 $user = User::register(array('nickname' => $nickname, | ||||
|                                                     'password' => $password, | ||||
|                                                     'email' => $email, | ||||
|                                                     'fullname' => $fullname, | ||||
|                                                     'homepage' => $homepage, | ||||
|                                                     'bio' => $bio, | ||||
|                                                     'location' => $location, | ||||
|                                                     'code' => $this->code)); | ||||
|                 Event::handle('EndRegistrationTry', array($this)); | ||||
|  | ||||
| 	        } else { | ||||
| 	            // TRANS: Form validation error displayed when trying to register with an invalid username or password. | ||||
| 	        	$this->clientError(_('Invalid username or password.'), 400); | ||||
|         	}	             | ||||
|         }  | ||||
|                 $this->initDocument('json'); | ||||
|                 $this->showJsonObjects($this->twitterUserArray($user->getProfile())); | ||||
|                 $this->endDocument('json'); | ||||
|  | ||||
|             } catch (Exception $e) { | ||||
|                 $this->clientError($e->getMessage(), 400); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -29,9 +29,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Allows the authenticating users to follow (subscribe) the user specified in | ||||
| @@ -90,7 +88,7 @@ class ApiFriendshipsCreateAction extends ApiAuthAction | ||||
|             $this->clientError(_('Could not follow user: profile not found.'), 403); | ||||
|         } | ||||
|  | ||||
|         if ($this->user->isSubscribed($this->other)) { | ||||
|         if ($this->scoped->isSubscribed($this->other)) { | ||||
|             $errmsg = sprintf( | ||||
|                 // TRANS: Client error displayed when trying to follow a user that's already being followed. | ||||
|                 // TRANS: %s is the nickname of the user that is already being followed. | ||||
| @@ -101,9 +99,9 @@ class ApiFriendshipsCreateAction extends ApiAuthAction | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             Subscription::start($this->user->getProfile(), $this->other); | ||||
|         } catch (Exception $e) { | ||||
|             $this->clientError($e->getMessage(), 403); | ||||
|             Subscription::start($this->scoped, $this->other); | ||||
|         } catch (AlreadyFulfilledException $e) { | ||||
|             $this->clientError($e->getMessage(), 409); | ||||
|         } | ||||
|  | ||||
|         $this->initDocument($this->format); | ||||
|   | ||||
| @@ -29,9 +29,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Allows the authenticating users to unfollow (unsubscribe) the user specified in | ||||
| @@ -48,7 +46,9 @@ if (!defined('STATUSNET')) { | ||||
|  */ | ||||
| class ApiFriendshipsDestroyAction extends ApiAuthAction | ||||
| { | ||||
|     var $other  = null; | ||||
|     protected $needPost = true; | ||||
|  | ||||
|     protected $other = null; | ||||
|  | ||||
|     /** | ||||
|      * Take arguments for running | ||||
| @@ -58,12 +58,11 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction | ||||
|      * @return boolean success flag | ||||
|      * | ||||
|      */ | ||||
|     function prepare($args) | ||||
|     protected function prepare(array $args=array()) | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         $this->user   = $this->auth_user; | ||||
|         $this->other  = $this->getTargetProfile($this->arg('id')); | ||||
|         $this->other = $this->getTargetProfile($this->arg('id')); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| @@ -73,58 +72,40 @@ class ApiFriendshipsDestroyAction extends ApiAuthAction | ||||
|      * | ||||
|      * Check the format and show the user info | ||||
|      * | ||||
|      * @param array $args $_REQUEST data (unused) | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function handle($args) | ||||
|     protected function handle() | ||||
|     { | ||||
|         parent::handle($args); | ||||
|  | ||||
|         if ($_SERVER['REQUEST_METHOD'] != 'POST') { | ||||
|             $this->clientError( | ||||
|                 // TRANS: Client error. POST is a HTTP command. It should not be translated. | ||||
|                 _('This method requires a POST.'), | ||||
|                 400, | ||||
|                 $this->format | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|         parent::handle(); | ||||
|  | ||||
|         if (!in_array($this->format, array('xml', 'json'))) { | ||||
|             $this->clientError( | ||||
|                 // TRANS: Client error displayed when coming across a non-supported API method. | ||||
|                 _('API method not found.'), | ||||
|                 404, | ||||
|                 $this->format | ||||
|                 404 | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (empty($this->other)) { | ||||
|         if (!$this->other instanceof Profile) { | ||||
|             $this->clientError( | ||||
|                 // TRANS: Client error displayed when trying to unfollow a user that cannot be found. | ||||
|                 _('Could not unfollow user: User not found.'), | ||||
|                 403, | ||||
|                 $this->format | ||||
|                 403 | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Don't allow unsubscribing from yourself! | ||||
|  | ||||
|         if ($this->user->id == $this->other->id) { | ||||
|         if ($this->scoped->id == $this->other->id) { | ||||
|             $this->clientError( | ||||
|                 // TRANS: Client error displayed when trying to unfollow self. | ||||
|                 _("You cannot unfollow yourself."), | ||||
|                 403, | ||||
|                 $this->format | ||||
|                 403 | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // throws an exception on error | ||||
|         Subscription::cancel($this->user->getProfile(), $this->other); | ||||
|         Subscription::cancel($this->scoped, $this->other); | ||||
|  | ||||
|         $this->initDocument($this->format); | ||||
|         $this->showProfile($this->other, $this->format); | ||||
|   | ||||
| @@ -29,9 +29,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Tests for the existence of friendship between two users. Will return true if | ||||
| @@ -57,7 +55,7 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction | ||||
|      * | ||||
|      * @return boolean success flag | ||||
|      */ | ||||
|     function prepare($args) | ||||
|     protected function prepare(array $args=array()) | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
| @@ -72,22 +70,18 @@ class ApiFriendshipsExistsAction extends ApiPrivateAuthAction | ||||
|      * | ||||
|      * Check the format and show the user info | ||||
|      * | ||||
|      * @param array $args $_REQUEST data (unused) | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function handle($args) | ||||
|     protected function handle() | ||||
|     { | ||||
|         parent::handle($args); | ||||
|         parent::handle(); | ||||
|  | ||||
|         if (empty($this->profile_a) || empty($this->profile_b)) { | ||||
|             $this->clientError( | ||||
|                 // TRANS: Client error displayed when supplying invalid parameters to an API call checking if a friendship exists. | ||||
|                 _('Two valid IDs or nick names must be supplied.'), | ||||
|                 400, | ||||
|                 $this->format | ||||
|                 400 | ||||
|             ); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $result = Subscription::exists($this->profile_a, $this->profile_b); | ||||
|   | ||||
| @@ -29,9 +29,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Outputs detailed information about the relationship between two users | ||||
| @@ -56,7 +54,7 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction | ||||
|      * | ||||
|      * @return boolean success flag | ||||
|      */ | ||||
|     function prepare($args) | ||||
|     protected function prepare(array $args=array()) | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
| @@ -109,13 +107,11 @@ class ApiFriendshipsShowAction extends ApiBareAuthAction | ||||
|      * | ||||
|      * Check the format and show the user info | ||||
|      * | ||||
|      * @param array $args $_REQUEST data (unused) | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function handle($args) | ||||
|     protected function handle() | ||||
|     { | ||||
|         parent::handle($args); | ||||
|         parent::handle(); | ||||
|  | ||||
|         if (!in_array($this->format, array('xml', 'json'))) { | ||||
|             // TRANS: Client error displayed when coming across a non-supported API method. | ||||
|   | ||||
| @@ -79,6 +79,10 @@ class ApiTimelineUserAction extends ApiBareAuthAction | ||||
|             $this->clientError(_('No such user.'), 404); | ||||
|         } | ||||
|  | ||||
|         if (!$this->target->isLocal()) { | ||||
|             $this->serverError(_('Remote user timelines are not available here yet.'), 501); | ||||
|         } | ||||
|  | ||||
|         $this->notices = $this->getNotices(); | ||||
|  | ||||
|         return true; | ||||
| @@ -405,7 +409,7 @@ class ApiTimelineUserAction extends ApiBareAuthAction | ||||
|  | ||||
|         // Get (safe!) HTML and text versions of the content | ||||
|  | ||||
|         $rendered = $this->purify($sourceContent); | ||||
|         $rendered = common_purify($sourceContent); | ||||
|         $content = common_strip_html($rendered); | ||||
|  | ||||
|         $shortened = $this->auth_user->shortenLinks($content); | ||||
| @@ -504,13 +508,4 @@ class ApiTimelineUserAction extends ApiBareAuthAction | ||||
|  | ||||
|         return $saved; | ||||
|     } | ||||
|  | ||||
|     function purify($content) | ||||
|     { | ||||
|         require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; | ||||
|  | ||||
|         $config = array('safe' => 1, | ||||
|                         'deny_attribute' => 'id,style,on*'); | ||||
|         return htmLawed($content, $config); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -230,18 +230,11 @@ class AtompubsubscriptionfeedAction extends AtompubAction | ||||
|                 $this->clientError(sprintf(_('Unknown profile %s.'), $person->id)); | ||||
|             } | ||||
|  | ||||
|             if (Subscription::exists($this->_profile, $profile)) { | ||||
|             try { | ||||
|                 $sub = Subscription::start($this->_profile, $profile); | ||||
|             } catch (AlreadyFulfilledException $e) { | ||||
|                 // 409 Conflict | ||||
|                 // TRANS: Client error displayed trying to subscribe to an already subscribed profile. | ||||
|                 // TRANS: %s is the profile the user already has a subscription on. | ||||
|                 $this->clientError(sprintf(_('Already subscribed to %s.'), | ||||
|                                            $person->id), | ||||
|                                    409); | ||||
|             } | ||||
|  | ||||
|             if (Subscription::start($this->_profile, $profile)) { | ||||
|                 $sub = Subscription::pkeyGet(array('subscriber' => $this->_profile->id, | ||||
|                                                    'subscribed' => $profile->id)); | ||||
|                 $this->clientError($e->getMessage(), 409); | ||||
|             } | ||||
|  | ||||
|             Event::handle('EndAtomPubNewActivity', array($activity, $sub)); | ||||
|   | ||||
| @@ -62,6 +62,6 @@ class Attachment_thumbnailAction extends AttachmentAction | ||||
|             common_redirect($e->file->getUrl()); | ||||
|         } | ||||
|  | ||||
|         common_redirect($thumbnail->getUrl()); | ||||
|         common_redirect(File_thumbnail::url($thumbnail->filename)); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -28,13 +28,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET') && !defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| define('MAX_ORIGINAL', 480); | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Upload an avatar | ||||
| @@ -369,13 +363,27 @@ class AvatarsettingsAction extends SettingsAction | ||||
|         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; | ||||
|         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$file_d; | ||||
|         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$file_d; | ||||
|         $size = intval(min($dest_w, $dest_h, MAX_ORIGINAL)); | ||||
|         $size = intval(min($dest_w, $dest_h, common_config('avatar', 'maxsize'))); | ||||
|  | ||||
|         $box = array('width' => $size, 'height' => $size, | ||||
|                      'x' => $dest_x,   'y' => $dest_y, | ||||
|                      'w' => $dest_w,   'h' => $dest_h); | ||||
|  | ||||
|         $user = common_current_user(); | ||||
|         $profile = $user->getProfile(); | ||||
|  | ||||
|         $imagefile = new ImageFile($user->id, $filedata['filepath']); | ||||
|         $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h); | ||||
|         $imagefile = new ImageFile(null, $filedata['filepath']); | ||||
|         $filename = Avatar::filename($profile->getID(), image_type_to_extension($imagefile->preferredType()), | ||||
|                                      $size, common_timestamp()); | ||||
|         try { | ||||
|             $imagefile->resizeTo(Avatar::path($filename), $box); | ||||
|         } catch (UseFileAsThumbnailException $e) { | ||||
|             common_debug('Using uploaded avatar directly without resizing, copying it to: '.$filename); | ||||
|             if (!copy($filedata['filepath'], Avatar::path($filename))) { | ||||
|                 common_debug('Tried to copy image file '.$filedata['filepath'].' to destination '.Avatar::path($filename)); | ||||
|                 throw new ServerException('Could not copy file to destination.'); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($profile->setOriginal($filename)) { | ||||
|             @unlink($filedata['filepath']); | ||||
|   | ||||
| @@ -43,23 +43,17 @@ class CancelsubscriptionAction extends FormAction | ||||
| { | ||||
|     protected $needPost = true; | ||||
|  | ||||
|     protected function prepare(array $args=array()) | ||||
|     protected function doPreparation() | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         $profile_id = $this->int('unsubscribeto'); | ||||
|         $this->target = Profile::getKV('id', $profile_id); | ||||
|         if (!$this->target instanceof Profile) { | ||||
|             throw new NoProfileException($profile_id); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     protected function handlePost() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         parent::handlePost(); | ||||
|  | ||||
|         try { | ||||
|             $request = Subscription_queue::pkeyGet(array('subscriber' => $this->scoped->id, | ||||
|                                                          'subscribed' => $this->target->id)); | ||||
| @@ -70,7 +64,7 @@ class CancelsubscriptionAction extends FormAction | ||||
|             common_debug('Tried to cancel a non-existing pending subscription'); | ||||
|         } | ||||
|  | ||||
|         if (StatusNet::isAjax()) { | ||||
|         if (GNUsocial::isAjax()) { | ||||
|             $this->startHTML('text/xml;charset=utf-8'); | ||||
|             $this->elementStart('head'); | ||||
|             // TRANS: Title after unsubscribing from a group. | ||||
| @@ -82,10 +76,7 @@ class CancelsubscriptionAction extends FormAction | ||||
|             $this->elementEnd('body'); | ||||
|             $this->endHTML(); | ||||
|             exit(); | ||||
|         } else { | ||||
|             common_redirect(common_local_url('subscriptions', | ||||
|                                              array('nickname' => $this->scoped->nickname)), | ||||
|                             303); | ||||
|         } | ||||
|         common_redirect(common_local_url('subscriptions', array('nickname' => $this->scoped->getNickname())), 303); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -174,11 +174,15 @@ class DocNav extends Menu | ||||
| { | ||||
|     function show() | ||||
|     { | ||||
|         $stub = new HomeStubNav($this->action); | ||||
|         $this->submenu(_m('MENU','Home'), $stub); | ||||
|         if (Event::handle('StartDocNav', array($this))) { | ||||
|             $stub = new HomeStubNav($this->action); | ||||
|             $this->submenu(_m('MENU','Home'), $stub); | ||||
|  | ||||
|         $docs = new DocListNav($this->action); | ||||
|         $this->submenu(_m('MENU','Docs'), $docs); | ||||
|             $docs = new DocListNav($this->action); | ||||
|             $this->submenu(_m('MENU','Docs'), $docs); | ||||
|              | ||||
|             Event::handle('EndDocNav', array($this)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -91,7 +91,7 @@ class EmailsettingsAction extends SettingsAction | ||||
|      */ | ||||
|     function showContent() | ||||
|     { | ||||
|         $user = common_current_user(); | ||||
|         $user = $this->scoped->getUser(); | ||||
|  | ||||
|         $this->elementStart('form', array('method' => 'post', | ||||
|                                           'id' => 'form_settings_email', | ||||
| @@ -313,17 +313,15 @@ class EmailsettingsAction extends SettingsAction | ||||
|      */ | ||||
|     function savePreferences() | ||||
|     { | ||||
|         $user = common_current_user(); | ||||
|         $user = $this->scoped->getUser(); | ||||
|  | ||||
|         if (Event::handle('StartEmailSaveForm', array($this, $this->scoped))) { | ||||
|             $emailnotifysub   = $this->boolean('emailnotifysub'); | ||||
|             $emailnotifymsg   = $this->boolean('emailnotifymsg'); | ||||
|             $emailnotifynudge = $this->boolean('emailnotifynudge'); | ||||
|             $emailnotifyattn  = $this->boolean('emailnotifyattn'); | ||||
|             $emailmicroid     = $this->boolean('emailmicroid'); | ||||
|             $emailpost        = $this->boolean('emailpost'); | ||||
|  | ||||
|             assert(!is_null($user)); // should already be checked | ||||
|             $emailnotifysub   = $this->booleanintstring('emailnotifysub'); | ||||
|             $emailnotifymsg   = $this->booleanintstring('emailnotifymsg'); | ||||
|             $emailnotifynudge = $this->booleanintstring('emailnotifynudge'); | ||||
|             $emailnotifyattn  = $this->booleanintstring('emailnotifyattn'); | ||||
|             $emailmicroid     = $this->booleanintstring('emailmicroid'); | ||||
|             $emailpost        = $this->booleanintstring('emailpost'); | ||||
|  | ||||
|             $user->query('BEGIN'); | ||||
|  | ||||
| @@ -340,6 +338,7 @@ class EmailsettingsAction extends SettingsAction | ||||
|  | ||||
|             if ($result === false) { | ||||
|                 common_log_db_error($user, 'UPDATE', __FILE__); | ||||
|                 $user->query('ROLLBACK'); | ||||
|                 // TRANS: Server error thrown on database error updating e-mail preferences. | ||||
|                 $this->serverError(_('Could not update user.')); | ||||
|             } | ||||
|   | ||||
| @@ -28,13 +28,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET') && !defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| define('MAX_ORIGINAL', 480); | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Upload an avatar | ||||
| @@ -390,13 +384,20 @@ class GrouplogoAction extends GroupAction | ||||
|         $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; | ||||
|         $dest_w = $this->arg('avatar_crop_w') ? $this->arg('avatar_crop_w'):$filedata['width']; | ||||
|         $dest_h = $this->arg('avatar_crop_h') ? $this->arg('avatar_crop_h'):$filedata['height']; | ||||
|         $size = min($dest_w, $dest_h); | ||||
|         $size = ($size > MAX_ORIGINAL) ? MAX_ORIGINAL:$size; | ||||
|         $size = min($dest_w, $dest_h, common_config('avatar', 'maxsize')); | ||||
|         $box = array('width' => $size, 'height' => $size, | ||||
|                      'x' => $dest_x,   'y' => $dest_y, | ||||
|                      'w' => $dest_w,   'h' => $dest_h); | ||||
|  | ||||
|         $imagefile = new ImageFile($this->group->id, $filedata['filepath']); | ||||
|         $filename = $imagefile->resize($size, $dest_x, $dest_y, $dest_w, $dest_h); | ||||
|         $profile = $this->group->getProfile(); | ||||
|  | ||||
|         if ($this->group->setOriginal($filename)) { | ||||
|         $imagefile = new ImageFile(null, $filedata['filepath']); | ||||
|         $filename = Avatar::filename($profile->getID(), image_type_to_extension($imagefile->preferredType()), | ||||
|                                      $size, common_timestamp()); | ||||
|  | ||||
|         $imagefile->resizeTo(Avatar::path($filename), $box); | ||||
|  | ||||
|         if ($profile->setOriginal($filename)) { | ||||
|             @unlink($filedata['filepath']); | ||||
|             unset($_SESSION['FILEDATA']); | ||||
|             $this->mode = 'upload'; | ||||
|   | ||||
| @@ -118,7 +118,7 @@ class InviteAction extends Action | ||||
|                         $this->already[] = $other; | ||||
|                     } else { | ||||
|                         try { | ||||
|                             Subscription::start($profile, $other); | ||||
|                             Subscription::ensureStart($profile, $other); | ||||
|                             $this->subbed[] = $other; | ||||
|                         } catch (Exception $e) { | ||||
|                             // subscription failed, but keep working | ||||
|   | ||||
| @@ -36,24 +36,6 @@ class LoginAction extends FormAction | ||||
| { | ||||
|     protected $needLogin = false; | ||||
|  | ||||
|     /** | ||||
|      * Prepare page to run | ||||
|      * | ||||
|      * | ||||
|      * @param $args | ||||
|      * @return string title | ||||
|      */ | ||||
|     protected function prepare(array $args=array()) | ||||
|     { | ||||
|         // @todo this check should really be in index.php for all sensitive actions | ||||
|         $ssl = common_config('site', 'ssl'); | ||||
|         if (empty($_SERVER['HTTPS']) && ($ssl == 'always' || $ssl == 'sometimes')) { | ||||
|             common_redirect(common_local_url('login')); | ||||
|         } | ||||
|  | ||||
|         return parent::prepare($args); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handle input, produce output | ||||
|      * | ||||
| @@ -79,10 +61,8 @@ class LoginAction extends FormAction | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     protected function handlePost() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         parent::handlePost(); | ||||
|  | ||||
|         // XXX: login throttle | ||||
|  | ||||
|         $nickname = $this->trimmed('nickname'); | ||||
| @@ -122,22 +102,6 @@ class LoginAction extends FormAction | ||||
|         common_redirect($url, 303); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Store an error and show the page | ||||
|      * | ||||
|      * This used to show the whole page; now, it's just a wrapper | ||||
|      * that stores the error in an attribute. | ||||
|      * | ||||
|      * @param string $error error, if any. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function showForm($msg=null, $success=false) | ||||
|     { | ||||
|         common_ensure_session(); | ||||
|         return parent::showForm($msg, $success); | ||||
|     } | ||||
|  | ||||
|     function showScripts() | ||||
|     { | ||||
|         parent::showScripts(); | ||||
| @@ -208,7 +172,7 @@ class LoginAction extends FormAction | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function getInstructions() | ||||
|     protected function getInstructions() | ||||
|     { | ||||
|         if (common_logged_in() && !common_is_real_login() && | ||||
|             common_get_returnto()) { | ||||
|   | ||||
| @@ -28,9 +28,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Add a new application | ||||
| @@ -51,10 +49,8 @@ class NewApplicationAction extends FormAction | ||||
|         return _('New application'); | ||||
|     } | ||||
|  | ||||
|     protected function handlePost() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         parent::handlePost(); | ||||
|  | ||||
|         if ($this->arg('cancel')) { | ||||
|             common_redirect(common_local_url('oauthappssettings'), 303); | ||||
|         } elseif ($this->arg('save')) { | ||||
| @@ -65,27 +61,15 @@ class NewApplicationAction extends FormAction | ||||
|         $this->clientError(_('Unexpected form submission.')); | ||||
|     } | ||||
|  | ||||
|     function showForm($msg=null) | ||||
|     protected function getForm() | ||||
|     { | ||||
|         $this->msg = $msg; | ||||
|         $this->showPage(); | ||||
|         return new ApplicationEditForm($this); | ||||
|     } | ||||
|  | ||||
|     function showContent() | ||||
|     protected function getInstructions() | ||||
|     { | ||||
|         $form = new ApplicationEditForm($this); | ||||
|         $form->show(); | ||||
|     } | ||||
|  | ||||
|     function showPageNotice() | ||||
|     { | ||||
|         if ($this->msg) { | ||||
|             $this->element('p', 'error', $this->msg); | ||||
|         } else { | ||||
|             $this->element('p', 'instructions', | ||||
|                            // TRANS: Form instructions for registering a new application. | ||||
|                            _('Use this form to register a new application.')); | ||||
|         } | ||||
|         // TRANS: Form instructions for registering a new application. | ||||
|         return _('Use this form to register a new application.'); | ||||
|     } | ||||
|  | ||||
|     private function trySave() | ||||
| @@ -181,6 +165,7 @@ class NewApplicationAction extends FormAction | ||||
|  | ||||
|         if (!$result) { | ||||
|             common_log_db_error($consumer, 'INSERT', __FILE__); | ||||
|             $app->query('ROLLBACK'); | ||||
|             // TRANS: Server error displayed when an application could not be registered in the database through the "New application" form. | ||||
|             $this->serverError(_('Could not create application.')); | ||||
|         } | ||||
| @@ -191,9 +176,9 @@ class NewApplicationAction extends FormAction | ||||
|  | ||||
|         if (!$this->app_id) { | ||||
|             common_log_db_error($app, 'INSERT', __FILE__); | ||||
|             $app->query('ROLLBACK'); | ||||
|             // TRANS: Server error displayed when an application could not be registered in the database through the "New application" form. | ||||
|             $this->serverError(_('Could not create application.')); | ||||
|             $app->query('ROLLBACK'); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|   | ||||
| @@ -29,9 +29,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Add a new group | ||||
| @@ -48,6 +46,8 @@ class NewgroupAction extends FormAction | ||||
| { | ||||
|     protected $group; | ||||
|  | ||||
|     protected $form = 'GroupEdit'; | ||||
|  | ||||
|     function getGroup() { | ||||
|         return $this->group; | ||||
|     } | ||||
| @@ -58,39 +58,23 @@ class NewgroupAction extends FormAction | ||||
|         return _('New group'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Prepare to run | ||||
|      */ | ||||
|     protected function prepare(array $args=array()) | ||||
|     protected function doPreparation() | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         // $this->scoped is the current user profile | ||||
|         if (!$this->scoped->hasRight(Right::CREATEGROUP)) { | ||||
|             // TRANS: Client exception thrown when a user tries to create a group while banned. | ||||
|             $this->clientError(_('You are not allowed to create groups on this site.'), 403); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public function showContent() | ||||
|     protected function getInstructions() | ||||
|     { | ||||
|         $form = new GroupEditForm($this); | ||||
|         $form->show(); | ||||
|         // TRANS: Form instructions for group create form. | ||||
|         return _('Use this form to create a new group.'); | ||||
|     } | ||||
|  | ||||
|     public function showInstructions() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         $this->element('p', 'instructions', | ||||
|                        // TRANS: Form instructions for group create form. | ||||
|                        _('Use this form to create a new group.')); | ||||
|     } | ||||
|  | ||||
|     protected function handlePost() | ||||
|     { | ||||
|         parent::handlePost(); | ||||
|  | ||||
|         if (Event::handle('StartGroupSaveForm', array($this))) { | ||||
|             $nickname = Nickname::normalize($this->trimmed('newnickname'), true); | ||||
|  | ||||
| @@ -133,7 +117,6 @@ class NewgroupAction extends FormAction | ||||
|                                            'Too many aliases! Maximum %d allowed.', | ||||
|                                            common_config('group', 'maxaliases')), | ||||
|                                         common_config('group', 'maxaliases'))); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if ($private) { | ||||
|   | ||||
| @@ -30,9 +30,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * Action for posting new notices | ||||
| @@ -62,6 +60,9 @@ class NewnoticeAction extends FormAction | ||||
|             // TRANS: Page title after sending a notice. | ||||
|             return _('Notice posted'); | ||||
|         } | ||||
|         if ($this->int('inreplyto')) { | ||||
|             return _m('TITLE', 'New reply'); | ||||
|         } | ||||
|         // TRANS: Page title for sending a new notice. | ||||
|         return _m('TITLE','New notice'); | ||||
|     } | ||||
| @@ -80,7 +81,7 @@ class NewnoticeAction extends FormAction | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This handlePost saves a new notice, based on arguments | ||||
|      * This doPost saves a new notice, based on arguments | ||||
|      * | ||||
|      * If successful, will show the notice, or return an Ajax-y result. | ||||
|      * If not, it will show an error message -- possibly Ajax-y. | ||||
| @@ -90,17 +91,15 @@ class NewnoticeAction extends FormAction | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     protected function handlePost() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         parent::handlePost(); | ||||
|  | ||||
|         assert($this->scoped); // XXX: maybe an error instead... | ||||
|         assert($this->scoped instanceof Profile); // XXX: maybe an error instead... | ||||
|         $user = $this->scoped->getUser(); | ||||
|         $content = $this->trimmed('status_textarea'); | ||||
|         $options = array(); | ||||
|         Event::handle('StartSaveNewNoticeWeb', array($this, $user, &$content, &$options)); | ||||
|  | ||||
|         if (!$content) { | ||||
|         if (empty($content)) { | ||||
|             // TRANS: Client error displayed trying to send a notice without content. | ||||
|             $this->clientError(_('No content!')); | ||||
|         } | ||||
| @@ -110,7 +109,7 @@ class NewnoticeAction extends FormAction | ||||
|         $cmd = $inter->handle_command($user, $content); | ||||
|  | ||||
|         if ($cmd) { | ||||
|             if (StatusNet::isAjax()) { | ||||
|             if (GNUsocial::isAjax()) { | ||||
|                 $cmd->execute(new AjaxWebChannel($this)); | ||||
|             } else { | ||||
|                 $cmd->execute(new WebChannel($this)); | ||||
| @@ -128,7 +127,7 @@ class NewnoticeAction extends FormAction | ||||
|                                        Notice::maxContent())); | ||||
|         } | ||||
|  | ||||
|         $replyto = intval($this->trimmed('inreplyto')); | ||||
|         $replyto = $this->int('inreplyto'); | ||||
|         if ($replyto) { | ||||
|             $options['reply_to'] = $replyto; | ||||
|         } | ||||
| @@ -195,7 +194,7 @@ class NewnoticeAction extends FormAction | ||||
|  | ||||
|         Event::handle('EndSaveNewNoticeWeb', array($this, $user, &$content_shortened, &$options)); | ||||
|  | ||||
|         if (!StatusNet::isAjax()) { | ||||
|         if (!GNUsocial::isAjax()) { | ||||
|             $url = common_local_url('shownotice', array('notice' => $this->stored->id)); | ||||
|             common_redirect($url, 303); | ||||
|         } | ||||
|   | ||||
| @@ -82,8 +82,8 @@ class ProfilesettingsAction extends SettingsAction | ||||
|      */ | ||||
|     function showContent() | ||||
|     { | ||||
|         $user = common_current_user(); | ||||
|         $profile = $user->getProfile(); | ||||
|         $profile = $this->scoped; | ||||
|         $user = $this->scoped->getUser(); | ||||
|  | ||||
|         $this->elementStart('form', array('method' => 'post', | ||||
|                                           'id' => 'form_settings_profile', | ||||
| @@ -104,7 +104,9 @@ class ProfilesettingsAction extends SettingsAction | ||||
|                          // TRANS: Tooltip for field label in form for profile settings. | ||||
|                          _('1-64 lowercase letters or numbers, no punctuation or spaces.'), | ||||
|                          null, false,   // "name" (will be set to id), then "required" | ||||
|                          !common_config('profile', 'changenick') ? array('disabled'=>'disabled') : array()); | ||||
|                          !common_config('profile', 'changenick') | ||||
|                                         ? array('disabled' => 'disabled', 'placeholder' => null) | ||||
|                                         : array('placeholder' => null)); | ||||
|             $this->elementEnd('li'); | ||||
|             $this->elementStart('li'); | ||||
|             // TRANS: Field label in form for profile settings. | ||||
| @@ -260,9 +262,9 @@ class ProfilesettingsAction extends SettingsAction | ||||
|             $homepage = $this->trimmed('homepage'); | ||||
|             $bio = $this->trimmed('bio'); | ||||
|             $location = $this->trimmed('location'); | ||||
|             $autosubscribe = $this->boolean('autosubscribe'); | ||||
|             $autosubscribe = $this->booleanintstring('autosubscribe'); | ||||
|             $subscribe_policy = $this->trimmed('subscribe_policy'); | ||||
|             $private_stream = $this->boolean('private_stream'); | ||||
|             $private_stream = $this->booleanintstring('private_stream'); | ||||
|             $language = $this->trimmed('language'); | ||||
|             $timezone = $this->trimmed('timezone'); | ||||
|             $tagstring = $this->trimmed('tags'); | ||||
| @@ -398,7 +400,7 @@ class ProfilesettingsAction extends SettingsAction | ||||
|                     $orig = clone($prefs); | ||||
|                 } | ||||
|  | ||||
|                 $prefs->share_location = $this->boolean('sharelocation'); | ||||
|                 $prefs->share_location = $this->booleanintstring('sharelocation'); | ||||
|  | ||||
|                 if ($exists) { | ||||
|                     $result = $prefs->update($orig); | ||||
|   | ||||
| @@ -27,9 +27,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET') && !defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
| if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * An action for registering a new user account | ||||
| @@ -229,40 +227,38 @@ class RegisterAction extends Action | ||||
|             } else if ($password != $confirm) { | ||||
|                 // TRANS: Form validation error displayed when trying to register with non-matching passwords. | ||||
|                 $this->showForm(_('Passwords do not match.')); | ||||
|             } else if ($user = User::register(array('nickname' => $nickname, | ||||
|             } else { | ||||
|                 try { | ||||
|                     $user = User::register(array('nickname' => $nickname, | ||||
|                                                     'password' => $password, | ||||
|                                                     'email' => $email, | ||||
|                                                     'fullname' => $fullname, | ||||
|                                                     'homepage' => $homepage, | ||||
|                                                     'bio' => $bio, | ||||
|                                                     'location' => $location, | ||||
|                                                     'code' => $code))) { | ||||
|                 if (!($user instanceof User)) { | ||||
|                                                     'code' => $code)); | ||||
|                     // success! | ||||
|                     if (!common_set_user($user)) { | ||||
|                         // TRANS: Server error displayed when saving fails during user registration. | ||||
|                         $this->serverError(_('Error setting user.')); | ||||
|                     } | ||||
|                     // this is a real login | ||||
|                     common_real_login(true); | ||||
|                     if ($this->boolean('rememberme')) { | ||||
|                         common_debug('Adding rememberme cookie for ' . $nickname); | ||||
|                         common_rememberme($user); | ||||
|                     } | ||||
|  | ||||
|                     // Re-init language env in case it changed (not yet, but soon) | ||||
|                     common_init_language(); | ||||
|  | ||||
|                     Event::handle('EndRegistrationTry', array($this)); | ||||
|  | ||||
|                     $this->showSuccess(); | ||||
|                 } catch (Exception $e) { | ||||
|                     // TRANS: Form validation error displayed when trying to register with an invalid username or password. | ||||
|                     $this->showForm(_('Invalid username or password.')); | ||||
|                     return; | ||||
|                     $this->showForm($e->getMessage()); | ||||
|                 } | ||||
|                 // success! | ||||
|                 if (!common_set_user($user)) { | ||||
|                     // TRANS: Server error displayed when saving fails during user registration. | ||||
|                     $this->serverError(_('Error setting user.')); | ||||
|                 } | ||||
|                 // this is a real login | ||||
|                 common_real_login(true); | ||||
|                 if ($this->boolean('rememberme')) { | ||||
|                     common_debug('Adding rememberme cookie for ' . $nickname); | ||||
|                     common_rememberme($user); | ||||
|                 } | ||||
|  | ||||
|                 // Re-init language env in case it changed (not yet, but soon) | ||||
|                 common_init_language(); | ||||
|  | ||||
|                 Event::handle('EndRegistrationTry', array($this)); | ||||
|  | ||||
|                 $this->showSuccess(); | ||||
|             } else { | ||||
|                 // TRANS: Form validation error displayed when trying to register with an invalid username or password. | ||||
|                 $this->showForm(_('Invalid username or password.')); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -27,13 +27,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET') && !defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
| require_once INSTALLDIR.'/lib/personalgroupnav.php'; | ||||
| require_once INSTALLDIR.'/lib/noticelist.php'; | ||||
| require_once INSTALLDIR.'/lib/feedlist.php'; | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * List of replies | ||||
| @@ -44,72 +38,42 @@ require_once INSTALLDIR.'/lib/feedlist.php'; | ||||
|  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link     http://status.net/ | ||||
|  */ | ||||
| class RepliesAction extends Action | ||||
| class RepliesAction extends ManagedAction | ||||
| { | ||||
|     var $page = null; | ||||
|     var $notice; | ||||
|  | ||||
|     /** | ||||
|      * Prepare the object | ||||
|      * | ||||
|      * Check the input values and initialize the object. | ||||
|      * Shows an error page on bad input. | ||||
|      * | ||||
|      * @param array $args $_REQUEST data | ||||
|      * | ||||
|      * @return boolean success flag | ||||
|      */ | ||||
|     function prepare($args) | ||||
|     protected function doPreparation() | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         $nickname = common_canonical_nickname($this->arg('nickname')); | ||||
|  | ||||
|         $this->user = User::getKV('nickname', $nickname); | ||||
|  | ||||
|         if (!$this->user) { | ||||
|         if (!$this->user instanceof User) { | ||||
|             // TRANS: Client error displayed when trying to reply to a non-exsting user. | ||||
|             $this->clientError(_('No such user.')); | ||||
|         } | ||||
|  | ||||
|         $profile = $this->user->getProfile(); | ||||
|         $this->target = $this->user->getProfile(); | ||||
|  | ||||
|         if (!$profile) { | ||||
|         if (!$this->target instanceof Profile) { | ||||
|             // TRANS: Error message displayed when referring to a user without a profile. | ||||
|             $this->serverError(_('User has no profile.')); | ||||
|         } | ||||
|  | ||||
|         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; | ||||
|         $this->page = $this->int('page') ?: 1; | ||||
|  | ||||
|         common_set_returnto($this->selfUrl()); | ||||
|  | ||||
|         $stream = new ReplyNoticeStream($this->user->id, | ||||
|                                         Profile::current()); | ||||
|         $stream = new ReplyNoticeStream($this->target->getID(), $this->scoped); | ||||
|  | ||||
|         $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, | ||||
|                                             NOTICES_PER_PAGE + 1); | ||||
|  | ||||
|         if($this->page > 1 && $this->notice->N == 0){ | ||||
|         if ($this->page > 1 && $this->notice->N == 0) { | ||||
|             // TRANS: Client error when page not found (404) | ||||
|             $this->clientError(_('No such page.'), 404); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handle a request | ||||
|      * | ||||
|      * Just show the page. All args already handled. | ||||
|      * | ||||
|      * @param array $args $_REQUEST data | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function handle($args) | ||||
|     { | ||||
|         parent::handle($args); | ||||
|         $this->showPage(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -124,12 +88,12 @@ class RepliesAction extends Action | ||||
|         if ($this->page == 1) { | ||||
|             // TRANS: Title for first page of replies for a user. | ||||
|             // TRANS: %s is a user nickname. | ||||
|             return sprintf(_("Replies to %s"), $this->user->nickname); | ||||
|             return sprintf(_("Replies to %s"), $this->target->getNickname()); | ||||
|         } else { | ||||
|             // TRANS: Title for all but the first page of replies for a user. | ||||
|             // TRANS: %1$s is a user nickname, %2$d is a page number. | ||||
|             return sprintf(_('Replies to %1$s, page %2$d'), | ||||
|                            $this->user->nickname, | ||||
|                            $this->target->getNickname(), | ||||
|                            $this->page); | ||||
|         } | ||||
|     } | ||||
| @@ -144,7 +108,7 @@ class RepliesAction extends Action | ||||
|         return array(new Feed(Feed::JSON, | ||||
|                               common_local_url('ApiTimelineMentions', | ||||
|                                                array( | ||||
|                                                     'id' => $this->user->nickname, | ||||
|                                                     'id' => $this->target->getNickname(), | ||||
|                                                     'format' => 'as')), | ||||
|                               // TRANS: Link for feed with replies for a user. | ||||
|                               // TRANS: %s is a user nickname. | ||||
| @@ -152,38 +116,31 @@ class RepliesAction extends Action | ||||
|                                       $this->user->nickname)), | ||||
|                      new Feed(Feed::RSS1, | ||||
|                               common_local_url('repliesrss', | ||||
|                                                array('nickname' => $this->user->nickname)), | ||||
|                                                array('nickname' => $this->target->getNickname())), | ||||
|                               // TRANS: Link for feed with replies for a user. | ||||
|                               // TRANS: %s is a user nickname. | ||||
|                               sprintf(_('Replies feed for %s (RSS 1.0)'), | ||||
|                                       $this->user->nickname)), | ||||
|                                       $this->target->getNickname())), | ||||
|                      new Feed(Feed::RSS2, | ||||
|                               common_local_url('ApiTimelineMentions', | ||||
|                                                array( | ||||
|                                                     'id' => $this->user->nickname, | ||||
|                                                     'id' => $this->target->getNickname(), | ||||
|                                                     'format' => 'rss')), | ||||
|                               // TRANS: Link for feed with replies for a user. | ||||
|                               // TRANS: %s is a user nickname. | ||||
|                               sprintf(_('Replies feed for %s (RSS 2.0)'), | ||||
|                                       $this->user->nickname)), | ||||
|                                       $this->target->getNickname())), | ||||
|                      new Feed(Feed::ATOM, | ||||
|                               common_local_url('ApiTimelineMentions', | ||||
|                                                array( | ||||
|                                                     'id' => $this->user->nickname, | ||||
|                                                     'id' => $this->target->getNickname(), | ||||
|                                                     'format' => 'atom')), | ||||
|                               // TRANS: Link for feed with replies for a user. | ||||
|                               // TRANS: %s is a user nickname. | ||||
|                               sprintf(_('Replies feed for %s (Atom)'), | ||||
|                                     $this->user->nickname))); | ||||
|                                     $this->target->getNickname()))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Show the content | ||||
|      * | ||||
|      * A list of notices that are replies to the user, plus pagination. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function showContent() | ||||
|     { | ||||
|         $nl = new PrimaryNoticeList($this->notice, $this, array('show_n'=>NOTICES_PER_PAGE)); | ||||
| @@ -195,33 +152,30 @@ class RepliesAction extends Action | ||||
|  | ||||
|         $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, | ||||
|                           $this->page, 'replies', | ||||
|                           array('nickname' => $this->user->nickname)); | ||||
|                           array('nickname' => $this->target->getNickname())); | ||||
|     } | ||||
|  | ||||
|     function showEmptyListMessage() | ||||
|     { | ||||
|         // TRANS: Empty list message for page with replies for a user. | ||||
|         // TRANS: %1$s and %s$s are the user nickname. | ||||
|         $message = sprintf(_('This is the timeline showing replies to %1$s but %2$s hasn\'t received a notice to them yet.'), | ||||
|                            $this->user->nickname, | ||||
|                            $this->user->nickname) . ' '; | ||||
|         // TRANS: %1$s is the user nickname. | ||||
|         $message = sprintf(_('This is the timeline showing replies to %1$s but no notices have arrived yet.'), $this->target->getNickname()); | ||||
|         $message .= ' ';    // Spacing between this sentence and the next. | ||||
|  | ||||
|         if (common_logged_in()) { | ||||
|             $current_user = common_current_user(); | ||||
|             if ($this->user->id === $current_user->id) { | ||||
|             if ($this->target->getID() === $this->scoped->getID()) { | ||||
|                 // TRANS: Empty list message for page with replies for a user for the logged in user. | ||||
|                 // TRANS: This message contains a Markdown link in the form [link text](link). | ||||
|                 $message .= _('You can engage other users in a conversation, subscribe to more people or [join groups](%%action.groups%%).'); | ||||
|             } else { | ||||
|                 // TRANS: Empty list message for page with replies for a user for all logged in users but the user themselves. | ||||
|                 // TRANS: %1$s, %2$s and %3$s are a user nickname. This message contains a Markdown link in the form [link text](link). | ||||
|                 $message .= sprintf(_('You can try to [nudge %1$s](../%2$s) or [post something to them](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname); | ||||
|                 // TRANS: %1$s is a user nickname and %2$s is the same but with a prepended '@' character. This message contains a Markdown link in the form [link text](link). | ||||
|                 $message .= sprintf(_('You can try to [nudge %1$s](../%1$s) or [post something to them](%%%%action.newnotice%%%%?content=%2$s).'), $this->target->getNickname(), '@' . $this->target->getNickname()); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|         } else { | ||||
|             // TRANS: Empty list message for page with replies for a user for not logged in users. | ||||
|             // TRANS: %1$s is a user nickname. This message contains a Markdown link in the form [link text](link). | ||||
|             $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->user->nickname); | ||||
|             $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->target->getNickname()); | ||||
|         } | ||||
|  | ||||
|         $this->elementStart('div', 'guide'); | ||||
| @@ -229,7 +183,7 @@ class RepliesAction extends Action | ||||
|         $this->elementEnd('div'); | ||||
|     } | ||||
|  | ||||
|     function isReadOnly($args) | ||||
|     public function isReadOnly($args) | ||||
|     { | ||||
|         return true; | ||||
|     } | ||||
|   | ||||
| @@ -70,7 +70,7 @@ class ShownoticeAction extends ManagedAction | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|         if ($this->boolean('ajax')) { | ||||
|             StatusNet::setApi(true); | ||||
|             GNUsocial::setApi(true); | ||||
|         } | ||||
|  | ||||
|         $this->notice = $this->getNotice(); | ||||
|   | ||||
| @@ -28,15 +28,7 @@ | ||||
|  * @link      http://status.net/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('STATUSNET') && !defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
| require_once INSTALLDIR.'/lib/personalgroupnav.php'; | ||||
| require_once INSTALLDIR.'/lib/noticelist.php'; | ||||
| require_once INSTALLDIR.'/lib/profileminilist.php'; | ||||
| require_once INSTALLDIR.'/lib/groupminilist.php'; | ||||
| require_once INSTALLDIR.'/lib/feedlist.php'; | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| /** | ||||
|  * User profile page | ||||
| @@ -233,10 +225,7 @@ class ShowstreamAction extends ProfileAction | ||||
|  | ||||
|     function showNotices() | ||||
|     { | ||||
|         $pnl = null; | ||||
|         if (Event::handle('ShowStreamNoticeList', array($this->notice, $this, &$pnl))) { | ||||
|             $pnl = new ProfileNoticeList($this->notice, $this); | ||||
|         } | ||||
|         $pnl = new NoticeList($this->notice, $this); | ||||
|         $cnt = $pnl->show(); | ||||
|         if (0 == $cnt) { | ||||
|             $this->showEmptyListMessage(); | ||||
| @@ -293,57 +282,3 @@ class ShowstreamAction extends ProfileAction | ||||
|         return $options; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // We don't show the author for a profile, since we already know who it is! | ||||
|  | ||||
| /** | ||||
|  * Slightly modified from standard list; the author & avatar are hidden | ||||
|  * in CSS. We used to remove them here too, but as it turns out that | ||||
|  * confuses the inline reply code... and we hide them in CSS anyway | ||||
|  * since realtime updates come through in original form. | ||||
|  * | ||||
|  * Remaining customization right now is for the repeat marker, where | ||||
|  * it'll list who the original poster was instead of who did the repeat | ||||
|  * (since the repeater is you, and the repeatee isn't shown!) | ||||
|  * This will remain inconsistent if realtime updates come through, | ||||
|  * since those'll get rendered as a regular NoticeListItem. | ||||
|  */ | ||||
| class ProfileNoticeList extends NoticeList | ||||
| { | ||||
|     function newListItem($notice) | ||||
|     { | ||||
|         return new ProfileNoticeListItem($notice, $this->out); | ||||
|     } | ||||
| } | ||||
|  | ||||
| class ProfileNoticeListItem extends DoFollowListItem | ||||
| { | ||||
|     /** | ||||
|      * show a link to the author of repeat | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     function showRepeat() | ||||
|     { | ||||
|         if (!empty($this->repeat)) { | ||||
|  | ||||
|             // FIXME: this code is almost identical to default; need to refactor | ||||
|  | ||||
|             $attrs = array('href' => $this->profile->profileurl, | ||||
|                            'class' => 'url'); | ||||
|  | ||||
|             if (!empty($this->profile->fullname)) { | ||||
|                 $attrs['title'] = $this->profile->getFancyName(); | ||||
|             } | ||||
|  | ||||
|             $this->out->elementStart('span', 'repeat'); | ||||
|  | ||||
|             $text_link = XMLStringer::estring('a', $attrs, $this->profile->nickname); | ||||
|  | ||||
|             // TRANS: Link to the author of a repeated notice. %s is a linked nickname. | ||||
|             $this->out->raw(sprintf(_('Repeat of %s'), $text_link)); | ||||
|  | ||||
|             $this->out->elementEnd('span'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -122,7 +122,7 @@ class SubscribeAction extends Action | ||||
|     { | ||||
|         // Throws exception on error | ||||
|  | ||||
|         $sub = Subscription::start($this->user->getProfile(), | ||||
|         $sub = Subscription::ensureStart($this->user->getProfile(), | ||||
|                                    $this->other); | ||||
|  | ||||
|         if ($this->boolean('ajax')) { | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|  | ||||
| if (!defined('GNUSOCIAL')) { exit(1); } | ||||
|  | ||||
| require_once INSTALLDIR . '/lib/settingsaction.php'; | ||||
| require_once INSTALLDIR . '/lib/peopletags.php'; | ||||
|  | ||||
| class TagprofileAction extends FormAction | ||||
| @@ -29,36 +28,32 @@ class TagprofileAction extends FormAction | ||||
|     protected $target = null; | ||||
|     protected $form = 'TagProfile'; | ||||
|  | ||||
|     protected function prepare(array $args=array()) | ||||
|     protected function doPreparation() | ||||
|     { | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         $id = $this->trimmed('id'); | ||||
|         if (!$id) { | ||||
|             $this->target = null; | ||||
|         } else { | ||||
|         $uri = $this->trimmed('uri'); | ||||
|         if (!empty($id))  { | ||||
|             $this->target = Profile::getKV('id', $id); | ||||
|  | ||||
|             if (!$this->target instanceof Profile) { | ||||
|                 // TRANS: Client error displayed when referring to non-existing profile ID. | ||||
|                 $this->clientError(_('No profile with that ID.')); | ||||
|             } | ||||
|         } elseif (!empty($uri)) { | ||||
|             $this->target = Profile::fromUri($uri); | ||||
|         } else { | ||||
|             // TRANS: Client error displayed when trying to tag a user but no ID or profile is provided. | ||||
|             $this->clientError(_('No profile identifier provided.')); | ||||
|         } | ||||
|  | ||||
|         if ($this->target instanceof Profile && !$this->scoped->canTag($this->target)) { | ||||
|         if (!$this->scoped->canTag($this->target)) { | ||||
|             // TRANS: Client error displayed when trying to tag a user that cannot be tagged. | ||||
|             $this->clientError(_('You cannot tag this user.')); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|         $this->formOpts = $this->target; | ||||
|  | ||||
|     protected function handle() | ||||
|     { | ||||
|         if (Event::handle('StartTagProfileAction', array($this, $this->target))) { | ||||
|             parent::handle(); | ||||
|             Event::handle('EndTagProfileAction', array($this, $this->target)); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function title() | ||||
| @@ -115,17 +110,8 @@ class TagprofileAction extends FormAction | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected function getForm() | ||||
|     protected function doPost() | ||||
|     { | ||||
|         $class = $this->form.'Form'; | ||||
|         $form = new $class($this, $this->target); | ||||
|         return $form; | ||||
|     } | ||||
|  | ||||
|     protected function handlePost() | ||||
|     { | ||||
|         parent::handlePost();   // Does nothing for now | ||||
|  | ||||
|         $tagstring = $this->trimmed('tags'); | ||||
|         $token = $this->trimmed('token'); | ||||
|  | ||||
| @@ -144,22 +130,16 @@ class TagprofileAction extends FormAction | ||||
|                     if (!common_valid_profile_tag($tag)) { | ||||
|                         // TRANS: Form validation error displayed if a given tag is invalid. | ||||
|                         // TRANS: %s is the invalid tag. | ||||
|                         $this->showForm(sprintf(_('Invalid tag: "%s".'), $tag)); | ||||
|                         return; | ||||
|                         throw new ClientException(sprintf(_('Invalid tag: "%s".'), $tag)); | ||||
|                     } | ||||
|  | ||||
|                     $tag_priv[$tag] = $private; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             try { | ||||
|                 $result = Profile_tag::setTags($this->scoped->id, $this->target->id, $tags, $tag_priv); | ||||
|                 if (!$result) { | ||||
|                     throw new Exception('The tags could not be saved.'); | ||||
|                 } | ||||
|             } catch (Exception $e) { | ||||
|                 $this->showForm($e->getMessage()); | ||||
|                 return false; | ||||
|             $result = Profile_tag::setTags($this->scoped->getID(), $this->target->getID(), $tags, $tag_priv); | ||||
|             if (!$result) { | ||||
|                 throw new ServerException('The tags could not be saved.'); | ||||
|             } | ||||
|  | ||||
|             if ($this->boolean('ajax')) { | ||||
| @@ -188,17 +168,4 @@ class TagprofileAction extends FormAction | ||||
|             Event::handle('EndSavePeopletags', array($this, $tagstring)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function showPageNotice() | ||||
|     { | ||||
|         if ($this->error) { | ||||
|             $this->element('p', 'error', $this->error); | ||||
|         } else { | ||||
|             $this->elementStart('div', 'instructions'); | ||||
|             $this->element('p', null, | ||||
|                            // TRANS: Page notice. | ||||
|                            _('Use this form to add your subscribers or subscriptions to lists.')); | ||||
|             $this->elementEnd('div'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class Attention extends Managed_DataObject | ||||
|     public $__table = 'attention';  // table name | ||||
|     public $notice_id;              // int(4) primary_key not_null | ||||
|     public $profile_id;             // int(4) primary_key not_null | ||||
|     public $reason;                 // varchar(255) | ||||
|     public $reason;                 // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                // datetime()   not_null | ||||
|     public $modified;               // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -33,7 +33,7 @@ class Attention extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice_id to give attention'), | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'profile_id for feed receiver'), | ||||
|                 'reason' => array('type' => 'varchar', 'length' => 255, 'description' => 'Optional reason why this was brought to the attention of profile_id'), | ||||
|                 'reason' => array('type' => 'varchar', 'length' => 191, 'description' => 'Optional reason why this was brought to the attention of profile_id'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -15,8 +15,8 @@ class Avatar extends Managed_DataObject | ||||
|     public $width;                           // int(4)  primary_key not_null | ||||
|     public $height;                          // int(4)  primary_key not_null | ||||
|     public $mediatype;                       // varchar(32)   not_null | ||||
|     public $filename;                        // varchar(255) | ||||
|     public $url;                             // varchar(255)  unique_key | ||||
|     public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $url;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -32,14 +32,14 @@ class Avatar extends Managed_DataObject | ||||
|                 'width' => array('type' => 'int', 'not null' => true, 'description' => 'image width'), | ||||
|                 'height' => array('type' => 'int', 'not null' => true, 'description' => 'image height'), | ||||
|                 'mediatype' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'file type'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 255, 'description' => 'local filename, if local'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'avatar location'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'local filename, if local'), | ||||
|                 'url' => array('type' => 'text', 'description' => 'avatar location, not indexed - do not use in WHERE statement'), | ||||
|                 '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('profile_id', 'width', 'height'), | ||||
|             'unique keys' => array( | ||||
|                 'avatar_url_key' => array('url'), | ||||
| //                'avatar_filename_key' => array('filename'), | ||||
|             ), | ||||
|             'foreign keys' => array( | ||||
|                 'avatar_profile_id_fkey' => array('profile', array('profile_id' => 'id')), | ||||
| @@ -241,16 +241,21 @@ class Avatar extends Managed_DataObject | ||||
|             // TRANS: An error message when avatar size is unreasonable | ||||
|             throw new Exception(_m('Avatar size too large')); | ||||
|         } | ||||
|         // So far we only have square avatars and I don't have time to | ||||
|         // rewrite support for non-square ones right now ;) | ||||
|         $height = $width; | ||||
|  | ||||
|         $original = Avatar::getUploaded($target); | ||||
|  | ||||
|         $imagefile = new ImageFile($target->id, Avatar::path($original->filename)); | ||||
|         $filename = $imagefile->resize($width); | ||||
|         $imagefile = new ImageFile(null, Avatar::path($original->filename)); | ||||
|         $filename = Avatar::filename($target->getID(), image_type_to_extension($imagefile->preferredType()), | ||||
|                                      $width, common_timestamp()); | ||||
|         $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height)); | ||||
|  | ||||
|         $scaled = clone($original); | ||||
|         $scaled->original = false; | ||||
|         $scaled->width = $width; | ||||
|         $scaled->height = $width; | ||||
|         $scaled->height = $height; | ||||
|         $scaled->url = Avatar::url($filename); | ||||
|         $scaled->filename = $filename; | ||||
|         $scaled->created = common_sql_now(); | ||||
|   | ||||
| @@ -35,7 +35,7 @@ class Config extends Managed_DataObject | ||||
|     public $__table = 'config';                          // table name | ||||
|     public $section;                         // varchar(32)  primary_key not_null | ||||
|     public $setting;                         // varchar(32)  primary_key not_null | ||||
|     public $value;                           // varchar(255) | ||||
|     public $value;                           // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|  | ||||
|     /* the code above is auto generated do not remove the tag below */ | ||||
|     ###END_AUTOCODE | ||||
| @@ -46,7 +46,7 @@ class Config extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'section' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration section'), | ||||
|                 'setting' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'default' => '', 'description' => 'configuration setting'), | ||||
|                 'value' => array('type' => 'varchar', 'length' => 255, 'description' => 'configuration value'), | ||||
|                 'value' => array('type' => 'varchar', 'length' => 191, 'description' => 'configuration value'), | ||||
|             ), | ||||
|             'primary key' => array('section', 'setting'), | ||||
|         ); | ||||
|   | ||||
| @@ -12,8 +12,8 @@ class Confirm_address extends Managed_DataObject | ||||
|     public $__table = 'confirm_address';                 // table name | ||||
|     public $code;                            // varchar(32)  primary_key not_null | ||||
|     public $user_id;                         // int(4)   not_null | ||||
|     public $address;                         // varchar(255)   not_null | ||||
|     public $address_extra;                   // varchar(255)   not_null | ||||
|     public $address;                         // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $address_extra;                   // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $address_type;                    // varchar(8)   not_null | ||||
|     public $claimed;                         // datetime()   | ||||
|     public $sent;                            // datetime()   | ||||
| @@ -28,8 +28,8 @@ class Confirm_address extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'good random code'), | ||||
|                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user who requested confirmation'), | ||||
|                 'address' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'), | ||||
|                 'address_extra' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'carrier ID, for SMS'), | ||||
|                 'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'address (email, xmpp, SMS, etc.)'), | ||||
|                 'address_extra' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'carrier ID, for SMS'), | ||||
|                 'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'), | ||||
|                 'claimed' => array('type' => 'datetime', 'description' => 'date this was claimed for queueing'), | ||||
|                 'sent' => array('type' => 'datetime', 'description' => 'date this was sent for queueing'), | ||||
|   | ||||
| @@ -10,8 +10,8 @@ class Consumer extends Managed_DataObject | ||||
|     /* the code below is auto generated do not remove the above tag */ | ||||
|  | ||||
|     public $__table = 'consumer';                        // table name | ||||
|     public $consumer_key;                    // varchar(255)  primary_key not_null | ||||
|     public $consumer_secret;                 // varchar(255)   not_null | ||||
|     public $consumer_key;                    // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $consumer_secret;                 // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $seed;                            // char(32)   not_null | ||||
|     public $created;                         // datetime   not_null | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
| @@ -24,8 +24,8 @@ class Consumer extends Managed_DataObject | ||||
|         return array( | ||||
|             'description' => 'OAuth consumer record', | ||||
|             'fields' => array( | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'consumer_secret' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'secret value'), | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'consumer_secret' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'secret value'), | ||||
|                 'seed' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'seed for new tokens by this consumer'), | ||||
|  | ||||
|                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), | ||||
|   | ||||
| @@ -35,7 +35,7 @@ class Conversation extends Managed_DataObject | ||||
| { | ||||
|     public $__table = 'conversation';        // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime   not_null | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -44,7 +44,7 @@ class Conversation extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'not null' => true, 'description' => 'should be set from root notice id (since 2014-03-01 commit)'), | ||||
|                 'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 255, 'description' => 'URI of the conversation'), | ||||
|                 'uri' => array('type' => 'varchar', 'not null'=>true, 'length' => 191, 'description' => 'URI of the conversation'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -34,7 +34,7 @@ class Deleted_notice extends Managed_DataObject | ||||
|     public $__table = 'deleted_notice';                  // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $profile_id;                      // int(4)   not_null | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $deleted;                         // datetime()   not_null | ||||
|  | ||||
| @@ -47,7 +47,7 @@ class Deleted_notice extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'), | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), | ||||
|                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), | ||||
|                 'deleted' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), | ||||
|             ), | ||||
|   | ||||
							
								
								
									
										279
									
								
								classes/File.php
									
									
									
									
									
								
							
							
						
						| @@ -26,29 +26,36 @@ class File extends Managed_DataObject | ||||
| { | ||||
|     public $__table = 'file';                            // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $url;                             // varchar(255)  unique_key | ||||
|     public $urlhash;                         // varchar(64)  unique_key | ||||
|     public $url;                             // text | ||||
|     public $filehash;                        // varchar(64)     indexed | ||||
|     public $mimetype;                        // varchar(50) | ||||
|     public $size;                            // int(4) | ||||
|     public $title;                           // varchar(255) | ||||
|     public $title;                           // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $date;                            // int(4) | ||||
|     public $protected;                       // int(4) | ||||
|     public $filename;                        // varchar(255) | ||||
|     public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $width;                           // int(4) | ||||
|     public $height;                          // int(4) | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
|     const URLHASH_ALG = 'sha256'; | ||||
|     const FILEHASH_ALG = 'sha256'; | ||||
|  | ||||
|     public static function schemaDef() | ||||
|     { | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'serial', 'not null' => true), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'destination URL after following redirections'), | ||||
|                 'urlhash' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 of destination URL (url field)'), | ||||
|                 'url' => array('type' => 'text', 'description' => 'destination URL after following possible redirections'), | ||||
|                 'filehash' => array('type' => 'varchar', 'length' => 64, 'not null' => false, 'description' => 'sha256 of the file contents, only for locally stored files of course'), | ||||
|                 'mimetype' => array('type' => 'varchar', 'length' => 50, 'description' => 'mime type of resource'), | ||||
|                 'size' => array('type' => 'int', 'description' => 'size of resource when available'), | ||||
|                 'title' => array('type' => 'varchar', 'length' => 255, 'description' => 'title of resource when available'), | ||||
|                 'title' => array('type' => 'varchar', 'length' => 191, 'description' => 'title of resource when available'), | ||||
|                 'date' => array('type' => 'int', 'description' => 'date of resource according to http query'), | ||||
|                 'protected' => array('type' => 'int', 'description' => 'true when URL is private (needs login)'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 255, 'description' => 'if a local file, name of the file'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'if a local file, name of the file'), | ||||
|                 'width' => array('type' => 'int', 'description' => 'width in pixels, if it can be described as such and data is available'), | ||||
|                 'height' => array('type' => 'int', 'description' => 'height in pixels, if it can be described as such and data is available'), | ||||
|  | ||||
| @@ -56,7 +63,10 @@ class File extends Managed_DataObject | ||||
|             ), | ||||
|             'primary key' => array('id'), | ||||
|             'unique keys' => array( | ||||
|                 'file_url_key' => array('url'), | ||||
|                 'file_urlhash_key' => array('urlhash'), | ||||
|             ), | ||||
|             'indexes' => array( | ||||
|                 'file_filehash_idx' => array('filehash'), | ||||
|             ), | ||||
|         ); | ||||
|     } | ||||
| @@ -77,10 +87,11 @@ class File extends Managed_DataObject | ||||
|         // I don't know why we have to keep doing this but I'm adding this last check to avoid | ||||
|         // uniqueness bugs. | ||||
|  | ||||
|         $file = File::getKV('url', $given_url); | ||||
|         $file = File::getKV('urlhash', self::hashurl($given_url)); | ||||
|          | ||||
|         if (!$file instanceof File) { | ||||
|             $file = new File; | ||||
|             $file->urlhash = self::hashurl($given_url); | ||||
|             $file->url = $given_url; | ||||
|             if (!empty($redir_data['protected'])) $file->protected = $redir_data['protected']; | ||||
|             if (!empty($redir_data['title'])) $file->title = $redir_data['title']; | ||||
| @@ -122,51 +133,56 @@ class File extends Managed_DataObject | ||||
|             throw new ServerException('No canonical URL from given URL to process'); | ||||
|         } | ||||
|  | ||||
|         $file = File::getKV('url', $given_url); | ||||
|         if (!$file instanceof File) { | ||||
|         $file = null; | ||||
|  | ||||
|         try { | ||||
|             $file = File::getByUrl($given_url); | ||||
|         } catch (NoResultException $e) { | ||||
|             // First check if we have a lookup trace for this URL already | ||||
|             $file_redir = File_redirection::getKV('url', $given_url); | ||||
|             if ($file_redir instanceof File_redirection) { | ||||
|             try { | ||||
|                 $file_redir = File_redirection::getByUrl($given_url); | ||||
|                 $file = File::getKV('id', $file_redir->file_id); | ||||
|                 if (!$file instanceof File) { | ||||
|                     // File did not exist, let's clean up the File_redirection entry | ||||
|                     $file_redir->delete(); | ||||
|                 } | ||||
|             } catch (NoResultException $e) { | ||||
|                 // We just wanted to doublecheck whether a File_thumbnail we might've had | ||||
|                 // actually referenced an existing File object. | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // If we still don't have a File object, let's create one now! | ||||
|         if (!$file instanceof File) { | ||||
|             // @fixme for new URLs this also looks up non-redirect data | ||||
|             // such as target content type, size, etc, which we need | ||||
|             // for File::saveNew(); so we call it even if not following | ||||
|             // new redirects. | ||||
|             $redir_data = File_redirection::where($given_url); | ||||
|             if (is_array($redir_data)) { | ||||
|                 $redir_url = $redir_data['url']; | ||||
|             } elseif (is_string($redir_data)) { | ||||
|                 $redir_url = $redir_data; | ||||
|                 $redir_data = array(); | ||||
|             } else { | ||||
|                 // TRANS: Server exception thrown when a URL cannot be processed. | ||||
|                 throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url)); | ||||
|             } | ||||
|  | ||||
|             // If we still don't have a File object, let's create one now! | ||||
|             if (!$file instanceof File) { | ||||
|                 // @fixme for new URLs this also looks up non-redirect data | ||||
|                 // such as target content type, size, etc, which we need | ||||
|                 // for File::saveNew(); so we call it even if not following | ||||
|                 // new redirects. | ||||
|                 $redir_data = File_redirection::where($given_url); | ||||
|                 if (is_array($redir_data)) { | ||||
|                     $redir_url = $redir_data['url']; | ||||
|                 } elseif (is_string($redir_data)) { | ||||
|                     $redir_url = $redir_data; | ||||
|                     $redir_data = array(); | ||||
|                 } else { | ||||
|                     // TRANS: Server exception thrown when a URL cannot be processed. | ||||
|                     throw new ServerException(sprintf(_("Cannot process URL '%s'"), $given_url)); | ||||
|                 } | ||||
|  | ||||
|                 // TODO: max field length | ||||
|                 if ($redir_url === $given_url || strlen($redir_url) > 255 || !$followRedirects) { | ||||
|                     // Save the File object based on our lookup trace | ||||
|                     $file = File::saveNew($redir_data, $given_url); | ||||
|                 } else { | ||||
|                     // This seems kind of messed up... for now skipping this part | ||||
|                     // if we're already under a redirect, so we don't go into | ||||
|                     // horrible infinite loops if we've been given an unstable | ||||
|                     // redirect (where the final destination of the first request | ||||
|                     // doesn't match what we get when we ask for it again). | ||||
|                     // | ||||
|                     // Seen in the wild with clojure.org, which redirects through | ||||
|                     // wikispaces for auth and appends session data in the URL params. | ||||
|                     $file = self::processNew($redir_url, $notice_id, /*followRedirects*/false); | ||||
|                     File_redirection::saveNew($redir_data, $file->id, $given_url); | ||||
|                 } | ||||
|             if ($redir_url === $given_url || !$followRedirects) { | ||||
|                 // Save the File object based on our lookup trace | ||||
|                 $file = File::saveNew($redir_data, $given_url); | ||||
|             } else { | ||||
|                 // This seems kind of messed up... for now skipping this part | ||||
|                 // if we're already under a redirect, so we don't go into | ||||
|                 // horrible infinite loops if we've been given an unstable | ||||
|                 // redirect (where the final destination of the first request | ||||
|                 // doesn't match what we get when we ask for it again). | ||||
|                 // | ||||
|                 // Seen in the wild with clojure.org, which redirects through | ||||
|                 // wikispaces for auth and appends session data in the URL params. | ||||
|                 $file = self::processNew($redir_url, $notice_id, /*followRedirects*/false); | ||||
|                 File_redirection::saveNew($redir_data, $file->id, $given_url); | ||||
|             } | ||||
|  | ||||
|             if (!$file instanceof File) { | ||||
| @@ -237,12 +253,7 @@ class File extends Managed_DataObject | ||||
|  | ||||
|     static function filename(Profile $profile, $origname, $mimetype) | ||||
|     { | ||||
|         try { | ||||
|             $ext = common_supported_mime_to_ext($mimetype); | ||||
|         } catch (Exception $e) { | ||||
|             // We don't support this mimetype, but let's guess the extension | ||||
|             $ext = substr(strrchr($mimetype, '/'), 1); | ||||
|         } | ||||
|         $ext = self::guessMimeExtension($mimetype); | ||||
|  | ||||
|         // Normalize and make the original filename more URL friendly. | ||||
|         $origname = basename($origname, ".$ext"); | ||||
| @@ -263,6 +274,17 @@ class File extends Managed_DataObject | ||||
|         return $filename; | ||||
|     } | ||||
|  | ||||
|     static function guessMimeExtension($mimetype) | ||||
|     { | ||||
|         try { | ||||
|             $ext = common_supported_mime_to_ext($mimetype); | ||||
|         } catch (Exception $e) { | ||||
|             // We don't support this mimetype, but let's guess the extension | ||||
|             $ext = substr(strrchr($mimetype, '/'), 1); | ||||
|         } | ||||
|         return strtolower($ext); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Validation for as-saved base filenames | ||||
|      */ | ||||
| @@ -303,7 +325,7 @@ class File extends Managed_DataObject | ||||
|  | ||||
|         } | ||||
|  | ||||
|         if (StatusNet::useHTTPS()) { | ||||
|         if (GNUsocial::useHTTPS()) { | ||||
|  | ||||
|             $sslserver = common_config('attachments', 'sslserver'); | ||||
|  | ||||
| @@ -381,6 +403,10 @@ class File extends Managed_DataObject | ||||
|      * @param $crop   bool  Crop to the max-values' aspect ratio | ||||
|      * | ||||
|      * @return File_thumbnail | ||||
|      * | ||||
|      * @throws UseFileAsThumbnailException  if the file is considered an image itself and should be itself as thumbnail | ||||
|      * @throws UnsupportedMediaException    if, despite trying, we can't understand how to make a thumbnail for this format | ||||
|      * @throws ServerException              on various other errors | ||||
|      */ | ||||
|     public function getThumbnail($width=null, $height=null, $crop=false, $force_still=true) | ||||
|     { | ||||
| @@ -394,67 +420,16 @@ class File extends Managed_DataObject | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($width === null) { | ||||
|             $width = common_config('thumbnail', 'width'); | ||||
|             $height = common_config('thumbnail', 'height'); | ||||
|             $crop = common_config('thumbnail', 'crop'); | ||||
|         } | ||||
|  | ||||
|         if ($height === null) { | ||||
|             $height = $width; | ||||
|             $crop = true; | ||||
|         } | ||||
|  | ||||
|         // Get proper aspect ratio width and height before lookup | ||||
|         // We have to do it through an ImageFile object because of orientation etc. | ||||
|         // Only other solution would've been to rotate + rewrite uploaded files. | ||||
|         list($width, $height, $x, $y, $w, $h) = | ||||
|                                 $image->scaleToFit($width, $height, $crop); | ||||
|  | ||||
|         $params = array('file_id'=> $this->id, | ||||
|                         'width'  => $width, | ||||
|                         'height' => $height); | ||||
|         $thumb = File_thumbnail::pkeyGet($params); | ||||
|         if ($thumb instanceof File_thumbnail) { | ||||
|             return $thumb; | ||||
|         } | ||||
|  | ||||
|         // throws exception on failure to generate thumbnail | ||||
|         $outname = "thumb-{$width}x{$height}-" . $image->filename; | ||||
|         $outpath = self::path($outname); | ||||
|  | ||||
|         // The boundary box for our resizing | ||||
|         $box = array('width'=>$width, 'height'=>$height, | ||||
|                      'x'=>$x,         'y'=>$y, | ||||
|                      'w'=>$w,         'h'=>$h); | ||||
|  | ||||
|         // Doublecheck that parameters are sane and integers. | ||||
|         if ($box['width'] < 1 || $box['width'] > common_config('thumbnail', 'maxsize') | ||||
|                 || $box['height'] < 1 || $box['height'] > common_config('thumbnail', 'maxsize') | ||||
|                 || $box['w'] < 1 || $box['x'] >= $image->width | ||||
|                 || $box['h'] < 1 || $box['y'] >= $image->height) { | ||||
|             // Fail on bad width parameter. If this occurs, it's due to algorithm in ImageFile->scaleToFit | ||||
|             common_debug("Boundary box parameters for resize of {$image->filepath} : ".var_export($box,true)); | ||||
|             throw new ServerException('Bad thumbnail size parameters.'); | ||||
|         } | ||||
|  | ||||
|         common_debug(sprintf('Generating a thumbnail of File id==%u of size %ux%u', $this->id, $width, $height)); | ||||
|         // Perform resize and store into file | ||||
|         $image->resizeTo($outpath, $box); | ||||
|  | ||||
|         // Avoid deleting the original | ||||
|         if ($image->getPath() != self::path($image->filename)) { | ||||
|             $image->unlink(); | ||||
|         } | ||||
|         return File_thumbnail::saveThumbnail($this->id, | ||||
|                                       self::url($outname), | ||||
|                                       $width, $height, | ||||
|                                       $outname); | ||||
|         return $image->getFileThumbnail($width, $height, $crop); | ||||
|     } | ||||
|  | ||||
|     public function getPath() | ||||
|     { | ||||
|         return self::path($this->filename); | ||||
|         $filepath = self::path($this->filename); | ||||
|         if (!file_exists($filepath)) { | ||||
|             throw new FileNotFoundException($filepath); | ||||
|         } | ||||
|         return $filepath; | ||||
|     } | ||||
|  | ||||
|     public function getUrl() | ||||
| @@ -462,7 +437,7 @@ class File extends Managed_DataObject | ||||
|         if (!empty($this->filename)) { | ||||
|             // A locally stored file, so let's generate a URL for our instance. | ||||
|             $url = self::url($this->filename); | ||||
|             if ($url != $this->url) { | ||||
|             if (self::hashurl($url) !== $this->urlhash) { | ||||
|                 // For indexing purposes, in case we do a lookup on the 'url' field. | ||||
|                 // also we're fixing possible changes from http to https, or paths | ||||
|                 $this->updateUrl($url); | ||||
| @@ -474,16 +449,40 @@ class File extends Managed_DataObject | ||||
|         return $this->url; | ||||
|     } | ||||
|  | ||||
|     static public function getByUrl($url) | ||||
|     { | ||||
|         $file = new File(); | ||||
|         $file->urlhash = self::hashurl($url); | ||||
|         if (!$file->find(true)) { | ||||
|             throw new NoResultException($file); | ||||
|         } | ||||
|         return $file; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param   string  $hashstr    String of (preferrably lower case) hexadecimal characters, same as result of 'hash_file(...)' | ||||
|      */ | ||||
|     static public function getByHash($hashstr, $alg=File::FILEHASH_ALG) | ||||
|     { | ||||
|         $file = new File(); | ||||
|         $file->filehash = strtolower($hashstr); | ||||
|         if (!$file->find(true)) { | ||||
|             throw new NoResultException($file); | ||||
|         } | ||||
|         return $file; | ||||
|     } | ||||
|  | ||||
|     public function updateUrl($url) | ||||
|     { | ||||
|         $file = File::getKV('url', $url); | ||||
|         $file = File::getKV('urlhash', self::hashurl($url)); | ||||
|         if ($file instanceof File) { | ||||
|             throw new ServerException('URL already exists in DB'); | ||||
|         } | ||||
|         $sql = 'UPDATE %1$s SET url=%2$s WHERE url=%3$s;'; | ||||
|         $sql = 'UPDATE %1$s SET urlhash=%2$s, url=%3$s WHERE urlhash=%4$s;'; | ||||
|         $result = $this->query(sprintf($sql, $this->__table, | ||||
|                                              $this->_quote((string)self::hashurl($url)), | ||||
|                                              $this->_quote((string)$url), | ||||
|                                              $this->_quote((string)$this->url))); | ||||
|                                              $this->_quote((string)$this->urlhash))); | ||||
|         if ($result === false) { | ||||
|             common_log_db_error($this, 'UPDATE', __FILE__); | ||||
|             throw new ServerException("Could not UPDATE {$this->__table}.url"); | ||||
| @@ -502,9 +501,9 @@ class File extends Managed_DataObject | ||||
|  | ||||
|     function blowCache($last=false) | ||||
|     { | ||||
|         self::blow('file:notice-ids:%s', $this->url); | ||||
|         self::blow('file:notice-ids:%s', $this->urlhash); | ||||
|         if ($last) { | ||||
|             self::blow('file:notice-ids:%s;last', $this->url); | ||||
|             self::blow('file:notice-ids:%s;last', $this->urlhash); | ||||
|         } | ||||
|         self::blow('file:notice-count:%d', $this->id); | ||||
|     } | ||||
| @@ -582,4 +581,54 @@ class File extends Managed_DataObject | ||||
|  | ||||
|         return $title ?: null; | ||||
|     } | ||||
|  | ||||
|     static public function hashurl($url) | ||||
|     { | ||||
|         if (empty($url)) { | ||||
|             throw new Exception('No URL provided to hash algorithm.'); | ||||
|         } | ||||
|         return hash(self::URLHASH_ALG, $url); | ||||
|     } | ||||
|  | ||||
|     static public function beforeSchemaUpdate() | ||||
|     { | ||||
|         $table = strtolower(get_called_class()); | ||||
|         $schema = Schema::get(); | ||||
|         $schemadef = $schema->getTableDef($table); | ||||
|  | ||||
|         // 2015-02-19 We have to upgrade our table definitions to have the urlhash field populated | ||||
|         if (isset($schemadef['fields']['urlhash']) && isset($schemadef['unique keys']['file_urlhash_key'])) { | ||||
|             // We already have the urlhash field, so no need to migrate it. | ||||
|             return; | ||||
|         } | ||||
|         echo "\nFound old $table table, upgrading it to contain 'urlhash' field..."; | ||||
|         // We have to create a urlhash that is _not_ the primary key, | ||||
|         // transfer data and THEN run checkSchema | ||||
|         $schemadef['fields']['urlhash'] = array ( | ||||
|                                               'type' => 'varchar', | ||||
|                                               'length' => 64, | ||||
|                                               'not null' => true, | ||||
|                                               'description' => 'sha256 of destination URL (url field)', | ||||
|                                             ); | ||||
|         $schemadef['fields']['url'] = array ( | ||||
|                                               'type' => 'text', | ||||
|                                               'description' => 'destination URL after following possible redirections', | ||||
|                                             ); | ||||
|         unset($schemadef['unique keys']); | ||||
|         $schema->ensureTable($table, $schemadef); | ||||
|         echo "DONE.\n"; | ||||
|  | ||||
|         $classname = ucfirst($table); | ||||
|         $tablefix = new $classname; | ||||
|         // urlhash is hash('sha256', $url) in the File table | ||||
|         echo "Updating urlhash fields in $table table..."; | ||||
|         // Maybe very MySQL specific :( | ||||
|         $tablefix->query(sprintf('UPDATE %1$s SET %2$s=%3$s;', | ||||
|                             $schema->quoteIdentifier($table), | ||||
|                             'urlhash', | ||||
|                             // The line below is "result of sha256 on column `url`" | ||||
|                             'SHA2(url, 256)')); | ||||
|         echo "DONE.\n"; | ||||
|         echo "Resuming core schema upgrade..."; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -29,7 +29,8 @@ class File_redirection extends Managed_DataObject | ||||
|     /* the code below is auto generated do not remove the above tag */ | ||||
|  | ||||
|     public $__table = 'file_redirection';                // table name | ||||
|     public $url;                             // varchar(255)  primary_key not_null | ||||
|     public $urlhash;                         // varchar(64) primary_key not_null | ||||
|     public $url;                             // text | ||||
|     public $file_id;                         // int(4) | ||||
|     public $redirections;                    // int(4) | ||||
|     public $httpcode;                        // int(4) | ||||
| @@ -42,19 +43,30 @@ class File_redirection extends Managed_DataObject | ||||
|     { | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'short URL (or any other kind of redirect) for file (id)'), | ||||
|                 'urlhash' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'sha256 hash of the URL'), | ||||
|                 'url' => array('type' => 'text', 'description' => 'short URL (or any other kind of redirect) for file (id)'), | ||||
|                 'file_id' => array('type' => 'int', 'description' => 'short URL for what URL/file'), | ||||
|                 'redirections' => array('type' => 'int', 'description' => 'redirect count'), | ||||
|                 'httpcode' => array('type' => 'int', 'description' => 'HTTP status code (20x, 30x, etc.)'), | ||||
|                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), | ||||
|             ), | ||||
|             'primary key' => array('url'), | ||||
|             'primary key' => array('urlhash'), | ||||
|             'foreign keys' => array( | ||||
|                 'file_redirection_file_id_fkey' => array('file' => array('file_id' => 'id')), | ||||
|             ), | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     static public function getByUrl($url) | ||||
|     { | ||||
|         $file = new File_redirection(); | ||||
|         $file->urlhash = File::hashurl($url); | ||||
|         if (!$file->find(true)) { | ||||
|             throw new NoResultException($file); | ||||
|         } | ||||
|         return $file; | ||||
|     } | ||||
|  | ||||
|     static function _commonHttp($url, $redirs) { | ||||
|         $request = new HTTPClient($url); | ||||
|         $request->setConfig(array( | ||||
| @@ -161,17 +173,18 @@ class File_redirection extends Managed_DataObject | ||||
|      */ | ||||
|     public function where($in_url, $discover=true) { | ||||
|         // let's see if we know this... | ||||
|         $a = File::getKV('url', $in_url); | ||||
|  | ||||
|         if (!empty($a)) { | ||||
|         try { | ||||
|             $a = File::getByUrl($in_url); | ||||
|             // this is a direct link to $a->url | ||||
|             return $a->url; | ||||
|         } else { | ||||
|             $b = File_redirection::getKV('url', $in_url); | ||||
|             if (!empty($b)) { | ||||
|         } catch (NoResultException $e) { | ||||
|             try { | ||||
|                 $b = File_redirection::getByUrl($in_url); | ||||
|                 // this is a redirect to $b->file_id | ||||
|                 $a = File::getKV('id', $b->file_id); | ||||
|                 return $a->url; | ||||
|             } catch (NoResultException $e) { | ||||
|                 // Oh well, let's keep going | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -274,6 +287,7 @@ class File_redirection extends Managed_DataObject | ||||
|             $file_redir = File_redirection::getKV('url', $short_url); | ||||
|             if (!$file_redir instanceof File_redirection) { | ||||
|                 $file_redir = new File_redirection; | ||||
|                 $file_redir->urlhash = File::hashurl($short_url); | ||||
|                 $file_redir->url = $short_url; | ||||
|                 $file_redir->file_id = $file_id; | ||||
|                 $file_redir->insert(); | ||||
| @@ -334,10 +348,53 @@ class File_redirection extends Managed_DataObject | ||||
|  | ||||
|     function saveNew($data, $file_id, $url) { | ||||
|         $file_redir = new File_redirection; | ||||
|         $file_redir->urlhash = File::hashurl($short_url); | ||||
|         $file_redir->url = $url; | ||||
|         $file_redir->file_id = $file_id; | ||||
|         $file_redir->redirections = intval($data['redirects']); | ||||
|         $file_redir->httpcode = intval($data['code']); | ||||
|         $file_redir->insert(); | ||||
|     } | ||||
|  | ||||
|     static public function beforeSchemaUpdate() | ||||
|     { | ||||
|         $table = strtolower(get_called_class()); | ||||
|         $schema = Schema::get(); | ||||
|         $schemadef = $schema->getTableDef($table); | ||||
|  | ||||
|         // 2015-02-19 We have to upgrade our table definitions to have the urlhash field populated | ||||
|         if (isset($schemadef['fields']['urlhash']) && in_array('urlhash', $schemadef['primary key'])) { | ||||
|             // We already have the urlhash field, so no need to migrate it. | ||||
|             return; | ||||
|         } | ||||
|         echo "\nFound old $table table, upgrading it to contain 'urlhash' field..."; | ||||
|         // We have to create a urlhash that is _not_ the primary key, | ||||
|         // transfer data and THEN run checkSchema | ||||
|         $schemadef['fields']['urlhash'] = array ( | ||||
|                                               'type' => 'varchar', | ||||
|                                               'length' => 64, | ||||
|                                               'not null' => true, | ||||
|                                               'description' => 'sha256 hash of the URL', | ||||
|                                             ); | ||||
|         $schemadef['fields']['url'] = array ( | ||||
|                                               'type' => 'text', | ||||
|                                               'description' => 'short URL (or any other kind of redirect) for file (id)', | ||||
|                                             ); | ||||
|         unset($schemadef['primary key']); | ||||
|         $schema->ensureTable($table, $schemadef); | ||||
|         echo "DONE.\n"; | ||||
|  | ||||
|         $classname = ucfirst($table); | ||||
|         $tablefix = new $classname; | ||||
|         // urlhash is hash('sha256', $url) in the File table | ||||
|         echo "Updating urlhash fields in $table table..."; | ||||
|         // Maybe very MySQL specific :( | ||||
|         $tablefix->query(sprintf('UPDATE %1$s SET %2$s=%3$s;', | ||||
|                             $schema->quoteIdentifier($table), | ||||
|                             'urlhash', | ||||
|                             // The line below is "result of sha256 on column `url`" | ||||
|                             'SHA2(url, 256)')); | ||||
|         echo "DONE.\n"; | ||||
|         echo "Resuming core schema upgrade..."; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,8 +27,8 @@ class File_thumbnail extends Managed_DataObject | ||||
| { | ||||
|     public $__table = 'file_thumbnail';                  // table name | ||||
|     public $file_id;                         // int(4)  primary_key not_null | ||||
|     public $url;                             // varchar(255)  unique_key | ||||
|     public $filename;                        // varchar(255) | ||||
|     public $url;                             // text | ||||
|     public $filename;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $width;                           // int(4)  primary_key | ||||
|     public $height;                          // int(4)  primary_key | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
| @@ -38,8 +38,8 @@ class File_thumbnail extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'file_id' => array('type' => 'int', 'not null' => true, 'description' => 'thumbnail for what URL/file'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of thumbnail'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 255, 'description' => 'if stored locally, filename is put here'), | ||||
|                 'url' => array('type' => 'text', 'not null' => false, 'description' => 'URL of thumbnail'), | ||||
|                 'filename' => array('type' => 'varchar', 'length' => 191, 'description' => 'if stored locally, filename is put here'), | ||||
|                 'width' => array('type' => 'int', 'description' => 'width of thumbnail'), | ||||
|                 'height' => array('type' => 'int', 'description' => 'height of thumbnail'), | ||||
|                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), | ||||
| @@ -117,46 +117,42 @@ class File_thumbnail extends Managed_DataObject | ||||
|         return File::path($filename); | ||||
|     } | ||||
|  | ||||
|     static function url($filename) | ||||
|     { | ||||
|         // TODO: Store thumbnails in their own directory and don't use File::url here | ||||
|         return File::url($filename); | ||||
|     } | ||||
|  | ||||
|     public function getPath() | ||||
|     { | ||||
|         return self::path($this->filename); | ||||
|         $filepath = self::path($this->filename); | ||||
|         if (!file_exists($filepath)) { | ||||
|             throw new FileNotFoundException($filepath); | ||||
|         } | ||||
|         return $filepath; | ||||
|     } | ||||
|  | ||||
|     public function getUrl() | ||||
|     { | ||||
|         if (!empty($this->getFile()->filename)) { | ||||
|             // A locally stored File, so let's generate a URL for our instance. | ||||
|             $url = File::url($this->filename); | ||||
|             if ($url != $this->url) { | ||||
|                 // For indexing purposes, in case we do a lookup on the 'url' field. | ||||
|                 // also we're fixing possible changes from http to https, or paths | ||||
|                 $this->updateUrl($url); | ||||
|             // A locally stored File, so we can dynamically generate a URL. | ||||
|             if (!empty($this->url)) { | ||||
|                 // Let's just clear this field as there is no point in having it for local files. | ||||
|                 $orig = clone($this); | ||||
|                 $this->url = null; | ||||
|                 $this->update($orig); | ||||
|             } | ||||
|             return $url; | ||||
|             $url = common_local_url('attachment_thumbnail', array('attachment'=>$this->file_id)); | ||||
|             if (strpos($url, '?') === false) { | ||||
|                 $url .= '?'; | ||||
|             } | ||||
|             return $url . http_build_query(array('w'=>$this->width, 'h'=>$this->height)); | ||||
|         } | ||||
|  | ||||
|         // No local filename available, return the URL we have stored | ||||
|         return $this->url; | ||||
|     } | ||||
|  | ||||
|     public function updateUrl($url) | ||||
|     { | ||||
|         $file = File_thumbnail::getKV('url', $url); | ||||
|         if ($file instanceof File_thumbnail) { | ||||
|             throw new ServerException('URL already exists in DB'); | ||||
|         } | ||||
|         $sql = 'UPDATE %1$s SET url=%2$s WHERE url=%3$s;'; | ||||
|         $result = $this->query(sprintf($sql, $this->__table, | ||||
|                                              $this->_quote((string)$url), | ||||
|                                              $this->_quote((string)$this->url))); | ||||
|         if ($result === false) { | ||||
|             common_log_db_error($this, 'UPDATE', __FILE__); | ||||
|             throw new ServerException("Could not UPDATE {$this->__table}.url"); | ||||
|         } | ||||
|  | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     public function delete($useWhere=false) | ||||
|     { | ||||
|         if (!empty($this->filename) && file_exists(File_thumbnail::path($this->filename))) { | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class Foreign_link extends Managed_DataObject | ||||
|     public $user_id;                         // int(4)  primary_key not_null | ||||
|     public $foreign_id;                      // bigint(8)  primary_key not_null unsigned | ||||
|     public $service;                         // int(4)  primary_key not_null | ||||
|     public $credentials;                     // varchar(255) | ||||
|     public $credentials;                     // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $noticesync;                      // tinyint(1)   not_null default_1 | ||||
|     public $friendsync;                      // tinyint(1)   not_null default_2 | ||||
|     public $profilesync;                     // tinyint(1)   not_null default_1 | ||||
| @@ -32,7 +32,7 @@ class Foreign_link extends Managed_DataObject | ||||
|                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'link to user on this system, if exists'), | ||||
|                 'foreign_id' => array('type' => 'int', 'size' => 'big', 'unsigned' => true, 'not null' => true, 'description' => 'link to user on foreign service, if exists'), | ||||
|                 'service' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to service'), | ||||
|                 'credentials' => array('type' => 'varchar', 'length' => 255, 'description' => 'authc credentials, typically a password'), | ||||
|                 'credentials' => array('type' => 'varchar', 'length' => 191, 'description' => 'authc credentials, typically a password'), | ||||
|                 'noticesync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies'), | ||||
|                 'friendsync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 2, 'description' => 'friend synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'), | ||||
|                 'profilesync' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming'), | ||||
|   | ||||
| @@ -12,7 +12,7 @@ class Foreign_service extends Managed_DataObject | ||||
|     public $__table = 'foreign_service';                 // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $name;                            // varchar(32)  unique_key not_null | ||||
|     public $description;                     // varchar(255) | ||||
|     public $description;                     // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -25,7 +25,7 @@ class Foreign_service extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'not null' => true, 'description' => 'numeric key for service'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'name of the service'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 255, 'description' => 'description'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -12,8 +12,8 @@ class Foreign_user extends Managed_DataObject | ||||
|     public $__table = 'foreign_user';                    // table name | ||||
|     public $id;                              // bigint(8)  primary_key not_null | ||||
|     public $service;                         // int(4)  primary_key not_null | ||||
|     public $uri;                             // varchar(255)  unique_key not_null | ||||
|     public $nickname;                        // varchar(255) | ||||
|     public $uri;                             // varchar(191)  unique_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $nickname;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -26,8 +26,8 @@ class Foreign_user extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'size' => 'big', 'not null' => true, 'description' => 'unique numeric key on foreign service'), | ||||
|                 'service' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to service'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'identifying URI'), | ||||
|                 'nickname' => array('type' => 'varchar', 'length' => 255, 'description' => 'nickname on foreign service'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'identifying URI'), | ||||
|                 'nickname' => array('type' => 'varchar', 'length' => 191, 'description' => 'nickname on foreign service'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -12,7 +12,7 @@ class Group_member extends Managed_DataObject | ||||
|     public $group_id;                        // int(4)  primary_key not_null | ||||
|     public $profile_id;                      // int(4)  primary_key not_null | ||||
|     public $is_admin;                        // tinyint(1) | ||||
|     public $uri;                             // varchar(255) | ||||
|     public $uri;                             // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -26,7 +26,7 @@ class Group_member extends Managed_DataObject | ||||
|                 'group_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to user_group'), | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), | ||||
|                 'is_admin' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'is this user an admin?'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -12,7 +12,7 @@ class Invitation extends Managed_DataObject | ||||
|     public $__table = 'invitation';                      // table name | ||||
|     public $code;                            // varchar(32)  primary_key not_null | ||||
|     public $user_id;                         // int(4)   not_null | ||||
|     public $address;                         // varchar(255)  multiple_key not_null | ||||
|     public $address;                         // varchar(191)  multiple_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $address_type;                    // varchar(8)  multiple_key not_null | ||||
|     public $registered_user_id;              // int(4)   not_null | ||||
|     public $created;                         // datetime()   not_null | ||||
| @@ -34,7 +34,7 @@ class Invitation extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'random code for an invitation'), | ||||
|                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'who sent the invitation'), | ||||
|                 'address' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'invitation sent to'), | ||||
|                 'address' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'invitation sent to'), | ||||
|                 'address_type' => array('type' => 'varchar', 'length' => 8, 'not null' => true, 'description' => 'address type ("email", "xmpp", "sms")'), | ||||
|                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), | ||||
|                 'registered_user_id' => array('type' => 'int', 'not null' => false, 'description' => 'if the invitation is converted, who the new user is'), | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class Location_namespace extends Managed_DataObject | ||||
|  | ||||
|     public $__table = 'location_namespace';              // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $description;                     // varchar(255) | ||||
|     public $description;                     // varchar(191) | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -44,7 +44,7 @@ class Location_namespace extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity for this namespace'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 255, 'description' => 'description of the namespace'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description of the namespace'), | ||||
|                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the record was created'), | ||||
|                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), | ||||
|             ), | ||||
|   | ||||
| @@ -299,6 +299,11 @@ abstract class Managed_DataObject extends Memcached_DataObject | ||||
|         return $ckeys; | ||||
|     } | ||||
|  | ||||
|     public function escapedTableName() | ||||
|     { | ||||
|         return common_database_tablename($this->tableName()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an ID, checked that it is set and reasonably valid | ||||
|      * | ||||
| @@ -391,4 +396,9 @@ abstract class Managed_DataObject extends Memcached_DataObject | ||||
|         // @FIXME return true only if something changed (otherwise 0) | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     static public function beforeSchemaUpdate() | ||||
|     { | ||||
|         // NOOP | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -734,7 +734,7 @@ class Memcached_DataObject extends Safe_DataObject | ||||
|         return $string; | ||||
|     } | ||||
|  | ||||
|     // We overload so that 'SET NAMES "utf8"' is called for | ||||
|     // We overload so that 'SET NAMES "utf8mb4"' is called for | ||||
|     // each connection | ||||
|  | ||||
|     function _connect() | ||||
| @@ -784,9 +784,9 @@ class Memcached_DataObject extends Safe_DataObject | ||||
|                 $conn = $DB->connection; | ||||
|                 if (!empty($conn)) { | ||||
|                     if ($DB instanceof DB_mysqli || $DB instanceof MDB2_Driver_mysqli) { | ||||
|                         mysqli_set_charset($conn, 'utf8'); | ||||
|                         mysqli_set_charset($conn, 'utf8mb4'); | ||||
|                     } else if ($DB instanceof DB_mysql || $DB instanceof MDB2_Driver_mysql) { | ||||
|                         mysql_set_charset('utf8', $conn); | ||||
|                         mysql_set_charset('utf8mb4', $conn); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ class Nonce extends Managed_DataObject | ||||
|     /* the code below is auto generated do not remove the above tag */ | ||||
|  | ||||
|     public $__table = 'nonce';                           // table name | ||||
|     public $consumer_key;                    // varchar(255)  primary_key not_null | ||||
|     public $consumer_key;                    // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $tok;                             // char(32) | ||||
|     public $nonce;                           // char(32)  primary_key not_null | ||||
|     public $ts;                              // datetime()  primary_key not_null | ||||
| @@ -39,7 +39,7 @@ class Nonce extends Managed_DataObject | ||||
|         return array( | ||||
|             'description' => 'OAuth nonce record', | ||||
|             'fields' => array( | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'tok' => array('type' => 'char', 'length' => 32, 'description' => 'buggy old value, ignored'), | ||||
|                 'nonce' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'nonce'), | ||||
|                 'ts' => array('type' => 'datetime', 'not null' => true, 'description' => 'timestamp sent'), | ||||
|   | ||||
| @@ -55,10 +55,10 @@ class Notice extends Managed_DataObject | ||||
|     public $__table = 'notice';                          // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $profile_id;                      // int(4)  multiple_key not_null | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $content;                         // text | ||||
|     public $rendered;                        // text | ||||
|     public $url;                             // varchar(255) | ||||
|     public $url;                             // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime  multiple_key not_null default_0000-00-00%2000%3A00%3A00 | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|     public $reply_to;                        // int(4) | ||||
| @@ -70,8 +70,8 @@ class Notice extends Managed_DataObject | ||||
|     public $location_id;                     // int(4) | ||||
|     public $location_ns;                     // int(4) | ||||
|     public $repeat_of;                       // int(4) | ||||
|     public $verb;                            // varchar(255) | ||||
|     public $object_type;                     // varchar(255) | ||||
|     public $verb;                            // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $object_type;                     // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $scope;                           // int(4) | ||||
|  | ||||
|     /* the code above is auto generated do not remove the tag below */ | ||||
| @@ -83,10 +83,10 @@ class Notice extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'who made the update'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier, usually a tag URI'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier, usually a tag URI'), | ||||
|                 'content' => array('type' => 'text', 'description' => 'update content', 'collate' => 'utf8_general_ci'), | ||||
|                 'rendered' => array('type' => 'text', 'description' => 'HTML version of the content'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL of any attachment (image, video, bookmark, whatever)'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL of any attachment (image, video, bookmark, whatever)'), | ||||
|                 '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'), | ||||
|                 'reply_to' => array('type' => 'int', 'description' => 'notice replied to (usually a guess)'), | ||||
| @@ -98,8 +98,8 @@ class Notice extends Managed_DataObject | ||||
|                 'location_id' => array('type' => 'int', 'description' => 'location id if possible'), | ||||
|                 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), | ||||
|                 'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'), | ||||
|                 'object_type' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'), | ||||
|                 'verb' => array('type' => 'varchar', 'length' => 255, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'), | ||||
|                 'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'), | ||||
|                 'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'), | ||||
|                 'scope' => array('type' => 'int', | ||||
|                                  'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers; null = default'), | ||||
|             ), | ||||
| @@ -128,7 +128,7 @@ class Notice extends Managed_DataObject | ||||
|  | ||||
|         return $def; | ||||
|     } | ||||
| 	 | ||||
|  | ||||
|     /* Notice types */ | ||||
|     const LOCAL_PUBLIC    =  1; | ||||
|     const REMOTE          =  0; | ||||
| @@ -142,7 +142,7 @@ class Notice extends Managed_DataObject | ||||
|     const FOLLOWER_SCOPE  = 8; | ||||
|  | ||||
|     protected $_profile = array(); | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * Will always return a profile, if anything fails it will | ||||
|      * (through _setProfile) throw a NoProfileException. | ||||
| @@ -157,7 +157,7 @@ class Notice extends Managed_DataObject | ||||
|         } | ||||
|         return $this->_profile[$this->profile_id]; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public function _setProfile(Profile $profile=null) | ||||
|     { | ||||
|         if (!$profile instanceof Profile) { | ||||
| @@ -268,7 +268,7 @@ class Notice extends Managed_DataObject | ||||
|         } | ||||
|         return $title; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public function getContent() | ||||
|     { | ||||
|         return $this->content; | ||||
| @@ -313,6 +313,16 @@ class Notice extends Managed_DataObject | ||||
|         return $notice; | ||||
|     } | ||||
|  | ||||
|     public static function getById($id) | ||||
|     { | ||||
|         $notice = new Notice(); | ||||
|         $notice->id = $id; | ||||
|         if (!$notice->find(true)) { | ||||
|             throw new NoResultException($notice); | ||||
|         } | ||||
|         return $notice; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Extract #hashtags from this notice's content and save them to the database. | ||||
|      */ | ||||
| @@ -674,7 +684,7 @@ class Notice extends Managed_DataObject | ||||
|                 $notice->insert();  // throws exception on failure | ||||
|                 // If it's not part of a conversation, it's | ||||
|                 // the beginning of a new conversation. | ||||
|                 if (empty($notice->conversation)) {  | ||||
|                 if (empty($notice->conversation)) { | ||||
|                     $orig = clone($notice); | ||||
|                     // $act->context->conversation will be null if it was not provided | ||||
|                     $conv = Conversation::create($notice, $options['conversation']); | ||||
| @@ -777,7 +787,7 @@ class Notice extends Managed_DataObject | ||||
|                           'distribute' => true); | ||||
|  | ||||
|         // options will have default values when nothing has been supplied | ||||
|         $options = array_merge($defaults, $options);  | ||||
|         $options = array_merge($defaults, $options); | ||||
|         foreach (array_keys($defaults) as $key) { | ||||
|             // Only convert the keynames we specify ourselves from 'defaults' array into variables | ||||
|             $$key = $options[$key]; | ||||
| @@ -892,6 +902,12 @@ class Notice extends Managed_DataObject | ||||
|                 $stored->insert();    // throws exception on error | ||||
|                 $orig = clone($stored); // for updating later in this try clause | ||||
|  | ||||
|                 $object = null; | ||||
|                 Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); | ||||
|                 if (empty($object)) { | ||||
|                     throw new ServerException('Unsuccessful call to StoreActivityObject '.$stored->uri . ': '.$act->asString()); | ||||
|                 } | ||||
|  | ||||
|                 // If it's not part of a conversation, it's | ||||
|                 // the beginning of a new conversation. | ||||
|                 if (empty($stored->conversation)) { | ||||
| @@ -900,12 +916,6 @@ class Notice extends Managed_DataObject | ||||
|                     $stored->conversation = $conv->id; | ||||
|                 } | ||||
|  | ||||
|                 $object = null; | ||||
|                 Event::handle('StoreActivityObject', array($act, $stored, $options, &$object)); | ||||
|                 if (empty($object)) { | ||||
|                     throw new ServerException('No object from StoreActivityObject '.$stored->uri . ': '.$act->asString()); | ||||
|                 } | ||||
|                 $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true); | ||||
|                 $stored->update($orig); | ||||
|             } catch (Exception $e) { | ||||
|                 if (empty($stored->id)) { | ||||
| @@ -957,7 +967,7 @@ class Notice extends Managed_DataObject | ||||
|             // Prepare inbox delivery, may be queued to background. | ||||
|             $stored->distribute(); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         return $stored; | ||||
|     } | ||||
|  | ||||
| @@ -1067,13 +1077,9 @@ class Notice extends Managed_DataObject | ||||
|         } | ||||
|  | ||||
|         $args = func_get_args(); | ||||
|  | ||||
|         $format = array_shift($args); | ||||
|  | ||||
|         $keyPart = vsprintf($format, $args); | ||||
|  | ||||
|         $cacheKey = Cache::key($keyPart); | ||||
|          | ||||
|         $c->delete($cacheKey); | ||||
|  | ||||
|         // delete the "last" stream, too, if this notice is | ||||
| @@ -1187,24 +1193,20 @@ class Notice extends Managed_DataObject | ||||
|     } | ||||
|  | ||||
| 	protected $_attachments = array(); | ||||
| 	 | ||||
|  | ||||
|     function attachments() { | ||||
| 		if (isset($this->_attachments[$this->id])) { | ||||
|             return $this->_attachments[$this->id]; | ||||
|         } | ||||
| 		 | ||||
|         $f2ps = File_to_post::listGet('post_id', array($this->id)); | ||||
| 		 | ||||
| 		$ids = array(); | ||||
| 		 | ||||
| 		foreach ($f2ps[$this->id] as $f2p) { | ||||
|             $ids[] = $f2p->file_id;     | ||||
|         } | ||||
| 		 | ||||
| 		$files = File::multiGet('id', $ids); | ||||
|  | ||||
|         $f2ps = File_to_post::listGet('post_id', array($this->id)); | ||||
| 		$ids = array(); | ||||
| 		foreach ($f2ps[$this->id] as $f2p) { | ||||
|             $ids[] = $f2p->file_id; | ||||
|         } | ||||
|  | ||||
| 		$files = File::multiGet('id', $ids); | ||||
| 		$this->_attachments[$this->id] = $files->fetchAll(); | ||||
| 		 | ||||
|         return $this->_attachments[$this->id]; | ||||
|     } | ||||
|  | ||||
| @@ -1286,7 +1288,7 @@ class Notice extends Managed_DataObject | ||||
|             $root->free(); | ||||
|             return $root; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         if (is_null($profile)) { | ||||
|             $keypart = sprintf('notice:conversation_root:%d:null', $this->id); | ||||
|         } else { | ||||
| @@ -1294,7 +1296,7 @@ class Notice extends Managed_DataObject | ||||
|                                $this->id, | ||||
|                                $profile->id); | ||||
|         } | ||||
|              | ||||
|  | ||||
|         $root = self::cacheGet($keypart); | ||||
|  | ||||
|         if ($root !== false && $root->inScope($profile)) { | ||||
| @@ -1707,9 +1709,9 @@ class Notice extends Managed_DataObject | ||||
|     function getReplyProfiles() | ||||
|     { | ||||
|         $ids = $this->getReplies(); | ||||
|          | ||||
|  | ||||
|         $profiles = Profile::multiGet('id', $ids); | ||||
|          | ||||
|  | ||||
|         return $profiles->fetchAll(); | ||||
|     } | ||||
|  | ||||
| @@ -1747,9 +1749,9 @@ class Notice extends Managed_DataObject | ||||
|      * | ||||
|      * @return array of Group objects | ||||
|      */ | ||||
|      | ||||
|  | ||||
|     protected $_groups = array(); | ||||
|      | ||||
|  | ||||
|     function getGroups() | ||||
|     { | ||||
|         // Don't save groups for repeats | ||||
| @@ -1757,27 +1759,24 @@ class Notice extends Managed_DataObject | ||||
|         if (!empty($this->repeat_of)) { | ||||
|             return array(); | ||||
|         } | ||||
|          | ||||
|  | ||||
|         if (isset($this->_groups[$this->id])) { | ||||
|             return $this->_groups[$this->id]; | ||||
|         } | ||||
|          | ||||
|  | ||||
|         $gis = Group_inbox::listGet('notice_id', array($this->id)); | ||||
|  | ||||
|         $ids = array(); | ||||
|  | ||||
| 		foreach ($gis[$this->id] as $gi) | ||||
| 		{ | ||||
| 		foreach ($gis[$this->id] as $gi) { | ||||
| 		    $ids[] = $gi->group_id; | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		$groups = User_group::multiGet('id', $ids); | ||||
| 		 | ||||
| 		$this->_groups[$this->id] = $groups->fetchAll(); | ||||
| 		 | ||||
| 		return $this->_groups[$this->id]; | ||||
|     } | ||||
|      | ||||
|  | ||||
|     function _setGroups($groups) | ||||
|     { | ||||
|         $this->_groups[$this->id] = $groups; | ||||
| @@ -1819,17 +1818,7 @@ class Notice extends Managed_DataObject | ||||
|  | ||||
|             $act->verb = $this->verb; | ||||
|  | ||||
|             if ($this->repeat_of) { | ||||
|                 $repeated = Notice::getKV('id', $this->repeat_of); | ||||
|                 if ($repeated instanceof Notice) { | ||||
|                     // TRANS: A repeat activity's title. %1$s is repeater's nickname | ||||
|                     //        and %2$s is the repeated user's nickname. | ||||
|                     $act->title = sprintf(_('%1$s repeated a notice by %2$s'), | ||||
|                                           $this->getProfile()->getNickname(), | ||||
|                                           $repeated->getProfile()->getNickname()); | ||||
|                     $act->objects[] = $repeated->asActivity($scoped); | ||||
|                 } | ||||
|             } else { | ||||
|             if (!$this->repeat_of) { | ||||
|                 $act->objects[] = $this->asActivityObject(); | ||||
|             } | ||||
|  | ||||
| @@ -2159,7 +2148,7 @@ class Notice extends Managed_DataObject | ||||
|             // Unfortunately this is likely to lose tags or URLs | ||||
|             // at the end of long notices. | ||||
|             $content = mb_substr($content, 0, $maxlen - 4) . ' ...'; | ||||
|         }      | ||||
|         } | ||||
|  | ||||
|  | ||||
|         // Scope is same as this one's | ||||
| @@ -2687,89 +2676,69 @@ class Notice extends Managed_DataObject | ||||
|             $scope = self::defaultScope(); | ||||
|         } | ||||
|  | ||||
|         // If there's no scope, anyone (even anon) is in scope. | ||||
|  | ||||
|         if ($scope == 0) { // Not private | ||||
|  | ||||
|             return !$this->isHiddenSpam($profile); | ||||
|  | ||||
|         } else { // Private, somehow | ||||
|  | ||||
|             // If there's scope, anon cannot be in scope | ||||
|  | ||||
|             if (empty($profile)) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             // Author is always in scope | ||||
|  | ||||
|             if ($this->profile_id == $profile->id) { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             // Only for users on this site | ||||
|  | ||||
|             if (($scope & Notice::SITE_SCOPE) && !$profile->isLocal()) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             // Only for users mentioned in the notice | ||||
|  | ||||
|             if ($scope & Notice::ADDRESSEE_SCOPE) { | ||||
|  | ||||
|                 $reply = Reply::pkeyGet(array('notice_id' => $this->id, | ||||
|                                              'profile_id' => $profile->id)); | ||||
| 										  | ||||
|                 if (!$reply instanceof Reply) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Only for members of the given group | ||||
|  | ||||
|             if ($scope & Notice::GROUP_SCOPE) { | ||||
|  | ||||
|                 // XXX: just query for the single membership | ||||
|  | ||||
|                 $groups = $this->getGroups(); | ||||
|  | ||||
|                 $foundOne = false; | ||||
|  | ||||
|                 foreach ($groups as $group) { | ||||
|                     if ($profile->isMember($group)) { | ||||
|                         $foundOne = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (!$foundOne) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Only for followers of the author | ||||
|  | ||||
|             $author = null; | ||||
|  | ||||
|             if ($scope & Notice::FOLLOWER_SCOPE) { | ||||
|  | ||||
|                 try { | ||||
|                     $author = $this->getProfile(); | ||||
|                 } catch (Exception $e) { | ||||
|                     return false; | ||||
|                 } | ||||
|          | ||||
|                 if (!Subscription::exists($profile, $author)) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         if ($scope == 0 && !$this->getProfile()->isPrivateStream()) { // Not scoping, so it is public. | ||||
|             return !$this->isHiddenSpam($profile); | ||||
|         } | ||||
|  | ||||
|         // If there's scope, anon cannot be in scope | ||||
|         if (empty($profile)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Author is always in scope | ||||
|         if ($this->profile_id == $profile->id) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Only for users on this site | ||||
|         if (($scope & Notice::SITE_SCOPE) && !$profile->isLocal()) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Only for users mentioned in the notice | ||||
|         if ($scope & Notice::ADDRESSEE_SCOPE) { | ||||
|  | ||||
|             $reply = Reply::pkeyGet(array('notice_id' => $this->id, | ||||
|                                          'profile_id' => $profile->id)); | ||||
|  | ||||
|             if (!$reply instanceof Reply) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Only for members of the given group | ||||
|         if ($scope & Notice::GROUP_SCOPE) { | ||||
|  | ||||
|             // XXX: just query for the single membership | ||||
|  | ||||
|             $groups = $this->getGroups(); | ||||
|  | ||||
|             $foundOne = false; | ||||
|  | ||||
|             foreach ($groups as $group) { | ||||
|                 if ($profile->isMember($group)) { | ||||
|                     $foundOne = true; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!$foundOne) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($scope & Notice::FOLLOWER_SCOPE || $this->getProfile()->isPrivateStream()) { | ||||
|  | ||||
|             if (!Subscription::exists($profile, $this->getProfile())) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return !$this->isHiddenSpam($profile); | ||||
|     } | ||||
|  | ||||
|     function isHiddenSpam($profile) { | ||||
|          | ||||
|  | ||||
|         // Hide posts by silenced users from everyone but moderators. | ||||
|  | ||||
|         if (common_config('notice', 'hidespam')) { | ||||
| @@ -2819,7 +2788,7 @@ class Notice extends Managed_DataObject | ||||
|         $skip = array('_profile', '_groups', '_attachments', '_faves', '_replies', '_repeats'); | ||||
|         return array_diff($vars, $skip); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     static function defaultScope() | ||||
|     { | ||||
|     	$scope = common_config('notice', 'defaultscope'); | ||||
| @@ -2836,7 +2805,6 @@ class Notice extends Managed_DataObject | ||||
| 	static function fillProfiles($notices) | ||||
| 	{ | ||||
| 		$map = self::getProfiles($notices); | ||||
| 		 | ||||
| 		foreach ($notices as $entry=>$notice) { | ||||
|             try { | ||||
|     			if (array_key_exists($notice->profile_id, $map)) { | ||||
| @@ -2847,42 +2815,35 @@ class Notice extends Managed_DataObject | ||||
|                 unset($notices[$entry]); | ||||
|             } | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		return array_values($map); | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	static function getProfiles(&$notices) | ||||
| 	{ | ||||
| 		$ids = array(); | ||||
| 		foreach ($notices as $notice) { | ||||
| 			$ids[] = $notice->profile_id; | ||||
| 		} | ||||
| 		 | ||||
| 		$ids = array_unique($ids); | ||||
| 		 | ||||
| 		return Profile::pivotGet('id', $ids);  | ||||
| 		return Profile::pivotGet('id', $ids); | ||||
| 	} | ||||
| 	 | ||||
|  | ||||
| 	static function fillGroups(&$notices) | ||||
| 	{ | ||||
|         $ids = self::_idsOf($notices); | ||||
| 		 | ||||
|         $gis = Group_inbox::listGet('notice_id', $ids); | ||||
| 		 | ||||
|         $gids = array(); | ||||
|  | ||||
| 		foreach ($gis as $id => $gi) | ||||
| 		{ | ||||
| 		foreach ($gis as $id => $gi) { | ||||
| 		    foreach ($gi as $g) | ||||
| 		    { | ||||
| 		        $gids[] = $g->group_id; | ||||
| 		    } | ||||
| 		} | ||||
| 		 | ||||
|  | ||||
| 		$gids = array_unique($gids); | ||||
| 		 | ||||
| 		$group = User_group::pivotGet('id', $gids); | ||||
| 		 | ||||
| 		foreach ($notices as $notice) | ||||
| 		{ | ||||
| 			$grps = array(); | ||||
| @@ -2906,21 +2867,16 @@ class Notice extends Managed_DataObject | ||||
|     static function fillAttachments(&$notices) | ||||
|     { | ||||
|         $ids = self::_idsOf($notices); | ||||
|  | ||||
|         $f2pMap = File_to_post::listGet('post_id', $ids); | ||||
| 		 | ||||
| 		$fileIds = array(); | ||||
| 		 | ||||
| 		foreach ($f2pMap as $noticeId => $f2ps) { | ||||
|             foreach ($f2ps as $f2p) { | ||||
|                 $fileIds[] = $f2p->file_id;     | ||||
|                 $fileIds[] = $f2p->file_id; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $fileIds = array_unique($fileIds); | ||||
|  | ||||
| 		$fileMap = File::pivotGet('id', $fileIds); | ||||
|  | ||||
| 		foreach ($notices as $notice) | ||||
| 		{ | ||||
| 			$files = array(); | ||||
| @@ -2945,31 +2901,4 @@ class Notice extends Managed_DataObject | ||||
|             $notice->_setReplies($ids); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected $_repeats = array(); | ||||
|  | ||||
|     function getRepeats() | ||||
|     { | ||||
|         if (isset($this->_repeats[$this->id])) { | ||||
|             return $this->_repeats[$this->id]; | ||||
|         } | ||||
|         $repeatMap = Notice::listGet('repeat_of', array($this->id)); | ||||
|         $this->_repeats[$this->id] = $repeatMap[$this->id]; | ||||
|         return $this->_repeats[$this->id]; | ||||
|     } | ||||
|  | ||||
|     function _setRepeats($repeats) | ||||
|     { | ||||
|         $this->_repeats[$this->id] = $repeats; | ||||
|     } | ||||
|  | ||||
|     static function fillRepeats(&$notices) | ||||
|     { | ||||
|         $ids = self::_idsOf($notices); | ||||
|         $repeatMap = Notice::listGet('repeat_of', $ids); | ||||
|         foreach ($notices as $notice) { | ||||
|         	$repeats = $repeatMap[$notice->id]; | ||||
|             $notice->_setRepeats($repeats); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -11,8 +11,8 @@ class Notice_source extends Managed_DataObject | ||||
|  | ||||
|     public $__table = 'notice_source';                   // table name | ||||
|     public $code;                            // varchar(32)  primary_key not_null | ||||
|     public $name;                            // varchar(255)   not_null | ||||
|     public $url;                             // varchar(255)   not_null | ||||
|     public $name;                            // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $url;                             // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -24,8 +24,8 @@ class Notice_source extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'code' => array('type' => 'varchar', 'length' => 32, 'not null' => true, 'description' => 'source code'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'name of the source'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'url to link to'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the source'), | ||||
|                 'url' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'url to link to'), | ||||
|                 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'date this record was created'), | ||||
|                 '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'), | ||||
|   | ||||
| @@ -12,14 +12,14 @@ class Oauth_application extends Managed_DataObject | ||||
|     public $__table = 'oauth_application';               // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $owner;                           // int(4)   not_null | ||||
|     public $consumer_key;                    // varchar(255)   not_null | ||||
|     public $name;                            // varchar(255)   not_null | ||||
|     public $description;                     // varchar(255) | ||||
|     public $icon;                            // varchar(255)   not_null | ||||
|     public $source_url;                      // varchar(255) | ||||
|     public $organization;                    // varchar(255) | ||||
|     public $homepage;                        // varchar(255) | ||||
|     public $callback_url;                    // varchar(255)   not_null | ||||
|     public $consumer_key;                    // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $name;                            // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $description;                     // varchar(191)              not 255 because utf8mb4 takes more space | ||||
|     public $icon;                            // varchar(191)              not_null   not 255 because utf8mb4 takes more space | ||||
|     public $source_url;                      // varchar(191)              not 255 because utf8mb4 takes more space | ||||
|     public $organization;                    // varchar(191)              not 255 because utf8mb4 takes more space | ||||
|     public $homepage;                        // varchar(191)              not 255 because utf8mb4 takes more space | ||||
|     public $callback_url;                    // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $type;                            // tinyint(1) | ||||
|     public $access_type;                     // tinyint(1) | ||||
|     public $created;                         // datetime   not_null | ||||
| @@ -43,12 +43,12 @@ class Oauth_application extends Managed_DataObject | ||||
|     static function maxDesc() | ||||
|     { | ||||
|         // This used to default to textlimit or allow unlimited descriptions, | ||||
|         // but this isn't part of a notice and the field's limited to 255 chars | ||||
|         // in the DB, so those seem silly. | ||||
|         // but this isn't part of a notice and the field's limited to 191 chars | ||||
|         // in the DB, so those seem silly. (utf8mb4 takes up more space, so can't use 255) | ||||
|         // | ||||
|         // Now just defaulting to 255 max unless a smaller application desclimit | ||||
|         // Now just defaulting to 191 max unless a smaller application desclimit | ||||
|         // is actually set. Setting to 0 will use the maximum. | ||||
|         $max = 255; | ||||
|         $max = 191; | ||||
|         $desclimit = intval(common_config('application', 'desclimit')); | ||||
|         if ($desclimit > 0 && $desclimit < $max) { | ||||
|             return $desclimit; | ||||
| @@ -80,7 +80,7 @@ class Oauth_application extends Managed_DataObject | ||||
|  | ||||
|     function setOriginal($filename) | ||||
|     { | ||||
|         $imagefile = new ImageFile($this->id, Avatar::path($filename)); | ||||
|         $imagefile = new ImageFile(null, Avatar::path($filename)); | ||||
|  | ||||
|         // XXX: Do we want to have a bunch of different size icons? homepage, stream, mini? | ||||
|         // or just one and control size via CSS? --Zach | ||||
| @@ -163,14 +163,14 @@ class Oauth_application extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), | ||||
|                 'owner' => array('type' => 'int', 'not null' => true, 'description' => 'owner of the application'), | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'application consumer key'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'name of the application'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 255, 'description' => 'description of the application'), | ||||
|                 'icon' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'application icon'), | ||||
|                 'source_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'application homepage - used for source link'), | ||||
|                 'organization' => array('type' => 'varchar', 'length' => 255, 'description' => 'name of the organization running the application'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 255, 'description' => 'homepage for the organization'), | ||||
|                 'callback_url' => array('type' => 'varchar', 'length' => 255, 'description' => 'url to redirect to after authentication'), | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application consumer key'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'name of the application'), | ||||
|                 'description' => array('type' => 'varchar', 'length' => 191, 'description' => 'description of the application'), | ||||
|                 'icon' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'application icon'), | ||||
|                 'source_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'application homepage - used for source link'), | ||||
|                 'organization' => array('type' => 'varchar', 'length' => 191, 'description' => 'name of the organization running the application'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage for the organization'), | ||||
|                 'callback_url' => array('type' => 'varchar', 'length' => 191, 'description' => 'url to redirect to after authentication'), | ||||
|                 'type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'type of app, 1 = browser, 2 = desktop'), | ||||
|                 'access_type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'default access type, bit 1 = read, bit 2 = write'), | ||||
|                 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date this record was created'), | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class Oauth_application_user extends Managed_DataObject | ||||
|     public $profile_id;                      // int(4)  primary_key not_null | ||||
|     public $application_id;                  // int(4)  primary_key not_null | ||||
|     public $access_type;                     // tinyint(1) | ||||
|     public $token;                           // varchar(255) | ||||
|     public $token;                           // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime   not_null | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -27,7 +27,7 @@ class Oauth_application_user extends Managed_DataObject | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'user of the application'), | ||||
|                 'application_id' => array('type' => 'int', 'not null' => true, 'description' => 'id of the application'), | ||||
|                 'access_type' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'access type, bit 1 = read, bit 2 = write'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => 255, 'description' => 'request or access token'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => 191, 'description' => 'request or access token'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -12,7 +12,7 @@ class Oauth_token_association extends Managed_DataObject | ||||
|     public $__table = 'oauth_token_association';          // table name | ||||
|     public $profile_id;                      // int(4)  primary_key not_null | ||||
|     public $application_id;                  // int(4)  primary_key not_null | ||||
|     public $token;                           // varchar(255) primary key not null | ||||
|     public $token;                           // varchar(191) primary key not null   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime   not_null | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -43,7 +43,7 @@ class Oauth_token_association extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'associated user'), | ||||
|                 'application_id' => array('type' => 'int', 'not null' => true, 'description' => 'the application'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => '255', 'not null' => true, 'description' => 'token used for this association'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => '191', 'not null' => true, 'description' => 'token used for this association'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -30,11 +30,11 @@ class Profile extends Managed_DataObject | ||||
|     public $__table = 'profile';                         // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $nickname;                        // varchar(64)  multiple_key not_null | ||||
|     public $fullname;                        // varchar(255)  multiple_key | ||||
|     public $profileurl;                      // varchar(255) | ||||
|     public $homepage;                        // varchar(255)  multiple_key | ||||
|     public $fullname;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space | ||||
|     public $profileurl;                      // varchar(191)                 not 255 because utf8mb4 takes more space | ||||
|     public $homepage;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space | ||||
|     public $bio;                             // text()  multiple_key | ||||
|     public $location;                        // varchar(255)  multiple_key | ||||
|     public $location;                        // varchar(191)  multiple_key   not 255 because utf8mb4 takes more space | ||||
|     public $lat;                             // decimal(10,7) | ||||
|     public $lon;                             // decimal(10,7) | ||||
|     public $location_id;                     // int(4) | ||||
| @@ -49,11 +49,11 @@ class Profile extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'serial', 'not null' => true, 'description' => 'unique identifier'), | ||||
|                 'nickname' => array('type' => 'varchar', 'length' => 64, 'not null' => true, 'description' => 'nickname or username', 'collate' => 'utf8_general_ci'), | ||||
|                 'fullname' => array('type' => 'varchar', 'length' => 255, 'description' => 'display name', 'collate' => 'utf8_general_ci'), | ||||
|                 'profileurl' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL, cached so we dont regenerate'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 255, 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'), | ||||
|                 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name', 'collate' => 'utf8_general_ci'), | ||||
|                 'profileurl' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'identifying URL', 'collate' => 'utf8_general_ci'), | ||||
|                 'bio' => array('type' => 'text', 'description' => 'descriptive biography', 'collate' => 'utf8_general_ci'), | ||||
|                 'location' => array('type' => 'varchar', 'length' => 255, 'description' => 'physical location', 'collate' => 'utf8_general_ci'), | ||||
|                 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'physical location', 'collate' => 'utf8_general_ci'), | ||||
|                 'lat' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'latitude'), | ||||
|                 'lon' => array('type' => 'numeric', 'precision' => 10, 'scale' => 7, 'description' => 'longitude'), | ||||
|                 'location_id' => array('type' => 'int', 'description' => 'location id if possible'), | ||||
| @@ -160,7 +160,7 @@ class Profile extends Managed_DataObject | ||||
|             return $this->getGroup()->setOriginal($filename); | ||||
|         } | ||||
|  | ||||
|         $imagefile = new ImageFile($this->id, Avatar::path($filename)); | ||||
|         $imagefile = new ImageFile(null, Avatar::path($filename)); | ||||
|  | ||||
|         $avatar = new Avatar(); | ||||
|         $avatar->profile_id = $this->id; | ||||
| @@ -1451,6 +1451,12 @@ class Profile extends Managed_DataObject | ||||
|         return $feed; | ||||
|     } | ||||
|  | ||||
|     public 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.')); | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Get a Profile object by URI. Will call external plugins for help | ||||
|      * using the event StartGetProfileFromURI. | ||||
| @@ -1566,6 +1572,15 @@ class Profile extends Managed_DataObject | ||||
|         return $this->getUser()->shortenLinks($text, $always); | ||||
|     } | ||||
|  | ||||
|     public function isPrivateStream() | ||||
|     { | ||||
|         // We only know of public remote users as of yet... | ||||
|         if (!$this->isLocal()) { | ||||
|             return false; | ||||
|         } | ||||
|         return $this->getUser()->private_stream ? true : false; | ||||
|     } | ||||
|  | ||||
|     public function delPref($namespace, $topic) { | ||||
|         return Profile_prefs::setData($this, $namespace, $topic, null); | ||||
|     } | ||||
|   | ||||
| @@ -43,8 +43,8 @@ class Profile_list extends Managed_DataObject | ||||
|     public $private;                         // tinyint(1) | ||||
|     public $created;                         // datetime   not_null default_0000-00-00%2000%3A00%3A00 | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $mainpage;                        // varchar(255) | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $mainpage;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $tagged_count;                    // smallint | ||||
|     public $subscriber_count;                // smallint | ||||
|  | ||||
| @@ -64,8 +64,8 @@ class Profile_list extends Managed_DataObject | ||||
|                 'created' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was added'), | ||||
|                 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date the tag was modified'), | ||||
|  | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'), | ||||
|                 'mainpage' => array('type' => 'varchar', 'length' => 255, 'description' => 'page to link to'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), | ||||
|                 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page to link to'), | ||||
|                 'tagged_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of people tagged with this tag by this user'), | ||||
|                 'subscriber_count' => array('type' => 'int', 'default' => 0, 'description' => 'number of subscribers to this tag'), | ||||
|             ), | ||||
|   | ||||
| @@ -31,8 +31,8 @@ class Profile_prefs extends Managed_DataObject | ||||
| { | ||||
|     public $__table = 'profile_prefs';       // table name | ||||
|     public $profile_id;                      // int(4)  primary_key not_null | ||||
|     public $namespace;                       // varchar(255)  not_null | ||||
|     public $topic;                           // varchar(255)  not_null | ||||
|     public $namespace;                       // varchar(191)  not_null | ||||
|     public $topic;                           // varchar(191)  not_null | ||||
|     public $data;                            // text | ||||
|     public $created;                         // datetime   not_null default_0000-00-00%2000%3A00%3A00 | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
| @@ -42,8 +42,8 @@ class Profile_prefs extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), | ||||
|                 'namespace' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'namespace, like pluginname or category'), | ||||
|                 'topic' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'preference key, i.e. description, age...'), | ||||
|                 'namespace' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'namespace, like pluginname or category'), | ||||
|                 'topic' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'preference key, i.e. description, age...'), | ||||
|                 'data' => array('type' => 'blob', 'description' => 'topic data, may be anything'), | ||||
|                 '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'), | ||||
|   | ||||
| @@ -12,7 +12,7 @@ class Sms_carrier extends Managed_DataObject | ||||
|     public $__table = 'sms_carrier';                     // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $name;                            // varchar(64)  unique_key | ||||
|     public $email_pattern;                   // varchar(255)   not_null | ||||
|     public $email_pattern;                   // varchar(191)   not_null   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -30,7 +30,7 @@ class Sms_carrier extends Managed_DataObject | ||||
|             'fields' => array( | ||||
|                 'id' => array('type' => 'int', 'not null' => true, 'description' => 'primary key for SMS carrier'), | ||||
|                 'name' => array('type' => 'varchar', 'length' => 64, 'description' => 'name of the carrier'), | ||||
|                 'email_pattern' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'sprintf pattern for making an email address from a phone number'), | ||||
|                 'email_pattern' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'sprintf pattern for making an email address from a phone number'), | ||||
|                 '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'), | ||||
|             ), | ||||
|   | ||||
| @@ -29,15 +29,15 @@ class Status_network extends Safe_DataObject | ||||
|     public $__table = 'status_network';                  // table name | ||||
|     public $site_id;                         // int(4) primary_key not_null | ||||
|     public $nickname;                        // varchar(64)   unique_key not_null | ||||
|     public $hostname;                        // varchar(255)  unique_key | ||||
|     public $pathname;                        // varchar(255)  unique_key | ||||
|     public $dbhost;                          // varchar(255) | ||||
|     public $dbuser;                          // varchar(255) | ||||
|     public $dbpass;                          // varchar(255) | ||||
|     public $dbname;                          // varchar(255) | ||||
|     public $sitename;                        // varchar(255) | ||||
|     public $theme;                           // varchar(255) | ||||
|     public $logo;                            // varchar(255) | ||||
|     public $hostname;                        // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $pathname;                        // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $dbhost;                          // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $dbuser;                          // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $dbpass;                          // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $dbname;                          // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $sitename;                        // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $theme;                           // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $logo;                            // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
|   | ||||
| @@ -32,9 +32,9 @@ class Subscription extends Managed_DataObject | ||||
|     public $subscribed;                      // int(4)  primary_key not_null | ||||
|     public $jabber;                          // tinyint(1)   default_1 | ||||
|     public $sms;                             // tinyint(1)   default_1 | ||||
|     public $token;                           // varchar(255) | ||||
|     public $secret;                          // varchar(255) | ||||
|     public $uri;                             // varchar(255) | ||||
|     public $token;                           // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $secret;                          // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $uri;                             // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -46,9 +46,9 @@ class Subscription extends Managed_DataObject | ||||
|                 'subscribed' => array('type' => 'int', 'not null' => true, 'description' => 'profile being listened to'), | ||||
|                 'jabber' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'deliver jabber messages'), | ||||
|                 'sms' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'deliver sms messages'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => 255, 'description' => 'authorization token'), | ||||
|                 'secret' => array('type' => 'varchar', 'length' => 255, 'description' => 'token secret'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universally unique identifier'), | ||||
|                 'token' => array('type' => 'varchar', 'length' => 191, 'description' => 'authorization token'), | ||||
|                 'secret' => array('type' => 'varchar', 'length' => 191, 'description' => 'token secret'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universally unique identifier'), | ||||
|                 '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'), | ||||
|             ), | ||||
| @@ -94,8 +94,12 @@ class Subscription extends Managed_DataObject | ||||
|         if (Event::handle('StartSubscribe', array($subscriber, $other))) { | ||||
|             $otherUser = User::getKV('id', $other->id); | ||||
|             if ($otherUser instanceof User && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) { | ||||
|                 $sub = Subscription_queue::saveNew($subscriber, $other); | ||||
|                 $sub->notify(); | ||||
|                 try { | ||||
|                     $sub = Subscription_queue::saveNew($subscriber, $other); | ||||
|                     $sub->notify(); | ||||
|                 } catch (AlreadyFulfilledException $e) { | ||||
|                     $sub = Subscription_queue::getSubQueue($subscriber, $other); | ||||
|                 } | ||||
|             } else { | ||||
|                 $sub = self::saveNew($subscriber->id, $other->id); | ||||
|                 $sub->notify(); | ||||
| @@ -124,7 +128,7 @@ class Subscription extends Managed_DataObject | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if ($sub instanceof Subscription) { // i.e. not SubscriptionQueue | ||||
|             if ($sub instanceof Subscription) { // i.e. not Subscription_queue | ||||
|                 Event::handle('EndSubscribe', array($subscriber, $other)); | ||||
|             } | ||||
|         } | ||||
| @@ -132,6 +136,16 @@ class Subscription extends Managed_DataObject | ||||
|         return $sub; | ||||
|     } | ||||
|  | ||||
|     static function ensureStart(Profile $subscriber, Profile $other, $force=false) | ||||
|     { | ||||
|         try { | ||||
|             $sub = self::start($subscriber, $other, $force); | ||||
|         } catch (AlreadyFulfilledException $e) { | ||||
|             return self::getSubscription($subscriber, $other); | ||||
|         } | ||||
|         return $sub; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Low-level subscription save. | ||||
|      * Outside callers should use Subscription::start() | ||||
| @@ -232,9 +246,25 @@ class Subscription extends Managed_DataObject | ||||
|  | ||||
|     static function exists(Profile $subscriber, Profile $other) | ||||
|     { | ||||
|         $sub = Subscription::pkeyGet(array('subscriber' => $subscriber->id, | ||||
|                                            'subscribed' => $other->id)); | ||||
|         return ($sub instanceof Subscription); | ||||
|         try { | ||||
|             $sub = self::getSubscription($subscriber, $other); | ||||
|         } catch (NoResultException $e) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     static function getSubscription(Profile $subscriber, Profile $other) | ||||
|     { | ||||
|         // This is essentially a pkeyGet but we have an object to return in NoResultException | ||||
|         $sub = new Subscription(); | ||||
|         $sub->subscriber = $subscriber->id; | ||||
|         $sub->subscribed = $other->id; | ||||
|         if (!$sub->find(true)) { | ||||
|             throw new NoResultException($sub); | ||||
|         } | ||||
|         return $sub; | ||||
|     } | ||||
|  | ||||
|     function asActivity() | ||||
|   | ||||
| @@ -36,6 +36,9 @@ class Subscription_queue extends Managed_DataObject | ||||
|  | ||||
|     public static function saveNew(Profile $subscriber, Profile $subscribed) | ||||
|     { | ||||
|         if (self::exists($subscriber, $subscribed)) { | ||||
|             throw new AlreadyFulfilledException(_('This subscription request is already in progress.')); | ||||
|         } | ||||
|         $rq = new Subscription_queue(); | ||||
|         $rq->subscriber = $subscriber->id; | ||||
|         $rq->subscribed = $subscribed->id; | ||||
| @@ -51,6 +54,18 @@ class Subscription_queue extends Managed_DataObject | ||||
|         return ($sub instanceof Subscription_queue); | ||||
|     } | ||||
|  | ||||
|     static function getSubQueue(Profile $subscriber, Profile $other) | ||||
|     { | ||||
|         // This is essentially a pkeyGet but we have an object to return in NoResultException | ||||
|         $sub = new Subscription_queue(); | ||||
|         $sub->subscriber = $subscriber->id; | ||||
|         $sub->subscribed = $other->id; | ||||
|         if (!$sub->find(true)) { | ||||
|             throw new NoResultException($sub); | ||||
|         } | ||||
|         return $sub; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Complete a pending subscription, as we've got approval of some sort. | ||||
|      * | ||||
|   | ||||
| @@ -10,13 +10,13 @@ class Token extends Managed_DataObject | ||||
|     /* the code below is auto generated do not remove the above tag */ | ||||
|  | ||||
|     public $__table = 'token';                           // table name | ||||
|     public $consumer_key;                    // varchar(255)  primary_key not_null | ||||
|     public $consumer_key;                    // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $tok;                             // char(32)  primary_key not_null | ||||
|     public $secret;                          // char(32)   not_null | ||||
|     public $type;                            // tinyint(1)   not_null | ||||
|     public $state;                           // tinyint(1) | ||||
|     public $verifier;                        // varchar(255) | ||||
|     public $verified_callback;               // varchar(255) | ||||
|     public $verifier;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $verified_callback;               // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -27,13 +27,13 @@ class Token extends Managed_DataObject | ||||
|         return array( | ||||
|             'description' => 'OAuth token record', | ||||
|             'fields' => array( | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'consumer_key' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'unique identifier, root URL'), | ||||
|                 'tok' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'identifying value'), | ||||
|                 'secret' => array('type' => 'char', 'length' => 32, 'not null' => true, 'description' => 'secret value'), | ||||
|                 'type' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'request or access'), | ||||
|                 'state' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'for requests, 0 = initial, 1 = authorized, 2 = used'), | ||||
|                 'verifier' => array('type' => 'varchar', 'length' => 255, 'description' => 'verifier string for OAuth 1.0a'), | ||||
|                 'verified_callback' => array('type' => 'varchar', 'length' => 255, 'description' => 'verified callback URL for OAuth 1.0a'), | ||||
|                 'verifier' => array('type' => 'varchar', 'length' => 191, 'description' => 'verifier string for OAuth 1.0a'), | ||||
|                 'verified_callback' => array('type' => 'varchar', 'length' => 191, 'description' => 'verified callback URL for OAuth 1.0a'), | ||||
|  | ||||
|                 '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'), | ||||
|   | ||||
| @@ -34,9 +34,9 @@ class User extends Managed_DataObject | ||||
|     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 $password;                        // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $email;                           // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $incomingemail;                   // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $emailnotifysub;                  // tinyint(1)   default_1 | ||||
|     public $emailnotifyfav;                  // tinyint(1)   default_1 | ||||
|     public $emailnotifynudge;                // tinyint(1)   default_1 | ||||
| @@ -50,8 +50,8 @@ class User extends Managed_DataObject | ||||
|     public $carrier;                         // int(4) | ||||
|     public $smsnotify;                       // tinyint(1) | ||||
|     public $smsreplies;                      // tinyint(1) | ||||
|     public $smsemail;                        // varchar(255) | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $smsemail;                        // varchar(191)               not 255 because utf8mb4 takes more space | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $autosubscribe;                   // tinyint(1) | ||||
|     public $subscribe_policy;                // tinyint(1) | ||||
|     public $urlshorteningservice;            // varchar(50)   default_ur1.ca | ||||
| @@ -69,9 +69,9 @@ class User extends Managed_DataObject | ||||
|             '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'), | ||||
|                 'password' => array('type' => 'varchar', 'length' => 191, 'description' => 'salted password, can be null for OpenID users'), | ||||
|                 'email' => array('type' => 'varchar', 'length' => 191, 'description' => 'email address for password recovery etc.'), | ||||
|                 'incomingemail' => array('type' => 'varchar', 'length' => 191, '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' => null, 'description' => 'Notify by email of favorites'), | ||||
|                 'emailnotifynudge' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of nudges'), | ||||
| @@ -85,8 +85,8 @@ class User extends Managed_DataObject | ||||
|                 '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'), | ||||
|                 'smsemail' => array('type' => 'varchar', 'length' => 191, 'description' => 'built from sms and carrier'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, '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'), | ||||
| @@ -191,7 +191,8 @@ class User extends Managed_DataObject | ||||
|      *              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 | ||||
|      * @return  User object | ||||
|      * @throws  Exception on failure | ||||
|      */ | ||||
|     static function register(array $fields) { | ||||
|  | ||||
| @@ -205,12 +206,8 @@ class User extends Managed_DataObject | ||||
|             $email = common_canonical_email($email); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             $profile->nickname = Nickname::normalize($nickname, true); | ||||
|         } catch (NicknameException $e) { | ||||
|             common_log(LOG_WARNING, sprintf('Bad nickname during User registration for %s: %s', $nickname, $e->getMessage()), __FILE__); | ||||
|             return false; | ||||
|         } | ||||
|         // Normalize _and_ check whether it is in use. Throw NicknameException on failure. | ||||
|         $profile->nickname = Nickname::normalize($nickname, true); | ||||
|  | ||||
|         $profile->profileurl = common_profile_url($profile->nickname); | ||||
|  | ||||
| @@ -277,7 +274,9 @@ class User extends Managed_DataObject | ||||
|             $id = $profile->insert(); | ||||
|             if ($id === false) { | ||||
|                 common_log_db_error($profile, 'INSERT', __FILE__); | ||||
|                 return false; | ||||
|                 $profile->query('ROLLBACK'); | ||||
|                 // TRANS: Profile data could not be inserted for some reason. | ||||
|                 throw new ServerException(_m('Could not insert profile data for new user.')); | ||||
|             } | ||||
|  | ||||
|             $user->id = $id; | ||||
| @@ -297,7 +296,8 @@ class User extends Managed_DataObject | ||||
|             if ($result === false) { | ||||
|                 common_log_db_error($user, 'INSERT', __FILE__); | ||||
|                 $profile->query('ROLLBACK'); | ||||
|                 return false; | ||||
|                 // TRANS: User data could not be inserted for some reason. | ||||
|                 throw new ServerException(_m('Could not insert user data for new user.')); | ||||
|             } | ||||
|  | ||||
|             // Everyone is subscribed to themself | ||||
| @@ -312,7 +312,8 @@ class User extends Managed_DataObject | ||||
|             if (!$result) { | ||||
|                 common_log_db_error($subscription, 'INSERT', __FILE__); | ||||
|                 $profile->query('ROLLBACK'); | ||||
|                 return false; | ||||
|                 // TRANS: Subscription data could not be inserted for some reason. | ||||
|                 throw new ServerException(_m('Could not insert subscription data for new user.')); | ||||
|             } | ||||
|  | ||||
|             // Mark that this invite was converted | ||||
| @@ -334,7 +335,8 @@ class User extends Managed_DataObject | ||||
|                 if (!$result) { | ||||
|                     common_log_db_error($confirm, 'INSERT', __FILE__); | ||||
|                     $profile->query('ROLLBACK'); | ||||
|                     return false; | ||||
|                     // TRANS: Email confirmation data could not be inserted for some reason. | ||||
|                     throw new ServerException(_m('Could not insert email confirmation data for new user.')); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -352,7 +354,7 @@ class User extends Managed_DataObject | ||||
|                     common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick), | ||||
|                                __FILE__); | ||||
|                 } else { | ||||
|                     Subscription::start($profile, $defuser->getProfile()); | ||||
|                     Subscription::ensureStart($profile, $defuser->getProfile()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -385,6 +387,10 @@ class User extends Managed_DataObject | ||||
|             Event::handle('EndUserRegister', array($profile)); | ||||
|         } | ||||
|  | ||||
|         if (!$user instanceof User) { | ||||
|             throw new ServerException('User could not be registered. Probably an event hook that failed.'); | ||||
|         } | ||||
|  | ||||
|         return $user; | ||||
|     } | ||||
|  | ||||
| @@ -687,11 +693,9 @@ class User extends Managed_DataObject | ||||
|         return $stream->getNotices($offset, $limit, $since_id, $max_id); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function repeatedToMe($offset=0, $limit=20, $since_id=null, $max_id=null) | ||||
|     public 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.')); | ||||
|         return $this->getProfile()->repeatedToMe($offset, $limit, $since_id, $max_id); | ||||
|     } | ||||
|  | ||||
|     public static function siteOwner() | ||||
| @@ -994,6 +998,11 @@ class User extends Managed_DataObject | ||||
|         return $act; | ||||
|     } | ||||
|  | ||||
|     public function isPrivateStream() | ||||
|     { | ||||
|         return $this->getProfile()->isPrivateStream(); | ||||
|     } | ||||
|  | ||||
|     public function delPref($namespace, $topic) | ||||
|     { | ||||
|         return $this->getProfile()->delPref($namespace, $topic); | ||||
|   | ||||
| @@ -15,18 +15,18 @@ class User_group extends Managed_DataObject | ||||
|     public $__table = 'user_group';                      // table name | ||||
|     public $id;                              // int(4)  primary_key not_null | ||||
|     public $nickname;                        // varchar(64) | ||||
|     public $fullname;                        // varchar(255) | ||||
|     public $homepage;                        // varchar(255) | ||||
|     public $fullname;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $homepage;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $description;                     // text | ||||
|     public $location;                        // varchar(255) | ||||
|     public $original_logo;                   // varchar(255) | ||||
|     public $homepage_logo;                   // varchar(255) | ||||
|     public $stream_logo;                     // varchar(255) | ||||
|     public $mini_logo;                       // varchar(255) | ||||
|     public $location;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $original_logo;                   // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $homepage_logo;                   // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $stream_logo;                     // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $mini_logo;                       // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $created;                         // datetime   not_null default_0000-00-00%2000%3A00%3A00 | ||||
|     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP | ||||
|     public $uri;                             // varchar(255)  unique_key | ||||
|     public $mainpage;                        // varchar(255) | ||||
|     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space | ||||
|     public $mainpage;                        // varchar(191)   not 255 because utf8mb4 takes more space | ||||
|     public $join_policy;                     // tinyint | ||||
|     public $force_scope;                     // tinyint | ||||
|  | ||||
| @@ -41,21 +41,21 @@ class User_group extends Managed_DataObject | ||||
|                 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'foreign key to profile table'), | ||||
|  | ||||
|                 'nickname' => array('type' => 'varchar', 'length' => 64, 'description' => 'nickname for addressing'), | ||||
|                 'fullname' => array('type' => 'varchar', 'length' => 255, 'description' => 'display name'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 255, 'description' => 'URL, cached so we dont regenerate'), | ||||
|                 'fullname' => array('type' => 'varchar', 'length' => 191, 'description' => 'display name'), | ||||
|                 'homepage' => array('type' => 'varchar', 'length' => 191, 'description' => 'URL, cached so we dont regenerate'), | ||||
|                 'description' => array('type' => 'text', 'description' => 'group description'), | ||||
|                 'location' => array('type' => 'varchar', 'length' => 255, 'description' => 'related physical location, if any'), | ||||
|                 'location' => array('type' => 'varchar', 'length' => 191, 'description' => 'related physical location, if any'), | ||||
|  | ||||
|                 'original_logo' => array('type' => 'varchar', 'length' => 255, 'description' => 'original size logo'), | ||||
|                 'homepage_logo' => array('type' => 'varchar', 'length' => 255, 'description' => 'homepage (profile) size logo'), | ||||
|                 'stream_logo' => array('type' => 'varchar', 'length' => 255, 'description' => 'stream-sized logo'), | ||||
|                 'mini_logo' => array('type' => 'varchar', 'length' => 255, 'description' => 'mini logo'), | ||||
|                 'original_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'original size logo'), | ||||
|                 'homepage_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'homepage (profile) size logo'), | ||||
|                 'stream_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'stream-sized logo'), | ||||
|                 'mini_logo' => array('type' => 'varchar', 'length' => 191, 'description' => 'mini logo'), | ||||
|  | ||||
|                 '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'), | ||||
|  | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 255, 'description' => 'universal identifier'), | ||||
|                 'mainpage' => array('type' => 'varchar', 'length' => 255, 'description' => 'page for group info to link to'), | ||||
|                 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'universal identifier'), | ||||
|                 'mainpage' => array('type' => 'varchar', 'length' => 191, 'description' => 'page for group info to link to'), | ||||
|                 'join_policy' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=open; 1=requires admin approval'),       | ||||
|                 'force_scope' => array('type' => 'int', 'size' => 'tiny', 'description' => '0=never,1=sometimes,-1=always'), | ||||
|             ), | ||||
| @@ -312,13 +312,21 @@ class User_group extends Managed_DataObject | ||||
|  | ||||
|     function setOriginal($filename) | ||||
|     { | ||||
|         $imagefile = new ImageFile($this->id, Avatar::path($filename)); | ||||
|         // This should be handled by the Profile->setOriginal function so user and group avatars are handled the same | ||||
|         $imagefile = new ImageFile(null, Avatar::path($filename)); | ||||
|  | ||||
|         $sizes = array('homepage_logo' => AVATAR_PROFILE_SIZE, | ||||
|                        'stream_logo' => AVATAR_STREAM_SIZE, | ||||
|                        'mini_logo' => AVATAR_MINI_SIZE); | ||||
|  | ||||
|         $orig = clone($this); | ||||
|         $this->original_logo = Avatar::url($filename); | ||||
|         $this->homepage_logo = Avatar::url($imagefile->resize(AVATAR_PROFILE_SIZE)); | ||||
|         $this->stream_logo = Avatar::url($imagefile->resize(AVATAR_STREAM_SIZE)); | ||||
|         $this->mini_logo = Avatar::url($imagefile->resize(AVATAR_MINI_SIZE)); | ||||
|         foreach ($sizes as $name=>$size) { | ||||
|             $filename = Avatar::filename($this->profile_id, image_type_to_extension($imagefile->preferredType()), | ||||
|                                          $size, common_timestamp()); | ||||
|             $imagefile->resizeTo(Avatar::path($filename), array('width'=>$size, 'height'=>$size)); | ||||
|             $this->$name = Avatar::url($filename); | ||||
|         } | ||||
|         common_debug(common_log_objstring($this)); | ||||
|         return $this->update($orig); | ||||
|     } | ||||
|   | ||||
| @@ -36,8 +36,8 @@ class User_im_prefs extends Managed_DataObject | ||||
|  | ||||
|     public $__table = 'user_im_prefs';       // table name | ||||
|     public $user_id;                         // int(4)  primary_key not_null | ||||
|     public $screenname;                      // varchar(255)  not_null | ||||
|     public $transport;                       // varchar(255)  not_null | ||||
|     public $screenname;                      // varchar(191)  not_null   not 255 because utf8mb4 takes more space | ||||
|     public $transport;                       // varchar(191)  not_null   not 255 because utf8mb4 takes more space | ||||
|     public $notify;                          // tinyint(1) | ||||
|     public $replies;                         // tinyint(1) | ||||
|     public $microid;                         // tinyint(1) | ||||
| @@ -53,8 +53,8 @@ class User_im_prefs extends Managed_DataObject | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'user'), | ||||
|                 'screenname' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'screenname on this service'), | ||||
|                 'transport' => array('type' => 'varchar', 'length' => 255, 'not null' => true, 'description' => 'transport (ex xmpp, aim)'), | ||||
|                 'screenname' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'screenname on this service'), | ||||
|                 'transport' => array('type' => 'varchar', 'length' => 191, 'not null' => true, 'description' => 'transport (ex xmpp, aim)'), | ||||
|                 'notify' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'Notify when a new notice is sent'), | ||||
|                 'replies' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'Send replies  from people not subscribed to'), | ||||
|                 'microid' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 1, 'description' => 'Publish a MicroID'), | ||||
|   | ||||
| @@ -11,8 +11,8 @@ class User_username extends Managed_DataObject | ||||
|  | ||||
|     public $__table = 'user_username';                     // table name | ||||
|     public $user_id;                        // int(4)  not_null | ||||
|     public $provider_name;                  // varchar(255)  primary_key not_null | ||||
|     public $username;                       // varchar(255)  primary_key not_null | ||||
|     public $provider_name;                  // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $username;                       // varchar(191)  primary_key not_null   not 255 because utf8mb4 takes more space | ||||
|     public $created;                        // datetime()   not_null | ||||
|     public $modified;                       // timestamp()   not_null default_CURRENT_TIMESTAMP | ||||
|  | ||||
| @@ -23,8 +23,8 @@ class User_username extends Managed_DataObject | ||||
|     { | ||||
|         return array( | ||||
|             'fields' => array( | ||||
|                 'provider_name' => array('type' => 'varchar', 'length' => 255, 'description' => 'provider name'), | ||||
|                 'username' => array('type' => 'varchar', 'length' => 255, 'description' => 'username'), | ||||
|                 'provider_name' => array('type' => 'varchar', 'length' => 191, 'description' => 'provider name'), | ||||
|                 'username' => array('type' => 'varchar', 'length' => 191, 'description' => 'username'), | ||||
|                 'user_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice id this title relates to'), | ||||
|                 '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'), | ||||
|   | ||||
| @@ -21,7 +21,7 @@ create table status_network ( | ||||
|     created datetime not null comment 'date this record was created', | ||||
|     modified timestamp comment 'date this record was modified' | ||||
|  | ||||
| ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci; | ||||
| ) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; | ||||
|  | ||||
| create table status_network_tag ( | ||||
|     site_id integer  comment 'unique id', | ||||
| @@ -30,5 +30,5 @@ create table status_network_tag ( | ||||
|  | ||||
|     constraint primary key (site_id, tag), | ||||
|     index status_network_tag_tag_idx (tag) | ||||
| ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci; | ||||
| ) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,31 @@ | ||||
| ### GNU social "fancy URL" setup | ||||
| # | ||||
| #   Change the "RewriteBase" in the new .htaccess file to be the URL path | ||||
| #       to your GNU Social installation on your server. Typically this will | ||||
| #       be the path to your GNU Social directory relative to your Web root. | ||||
| #       If you are installing it in the root directory, leave it as '/'. | ||||
| # | ||||
| #   If it doesn't work, double-check that AllowOverride for the GNU Social | ||||
| #       directory is 'All' in your Apache configuration file. This can be | ||||
| #       * /etc/apache2/apache2.conf (generic) | ||||
| #       * /etc/apache2/sites-available/default(on Debian and Ubuntu) | ||||
| #       * ...many other variations depending on distribution... | ||||
| # | ||||
| #   See the Apache documentation for .htaccess files for more details: | ||||
| #       https://httpd.apache.org/docs/2.4/howto/htaccess.html | ||||
| # | ||||
| #   Also, check that mod_rewrite is installed and enabled: | ||||
| #       https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html | ||||
|  | ||||
|  | ||||
| <IfModule mod_rewrite.c> | ||||
|   RewriteEngine On | ||||
|  | ||||
|   # NOTE: change this to your actual StatusNet base URL path, | ||||
|   # NOTE: change this to your actual GNU social base URL path, | ||||
|   # minus the domain part: | ||||
|   # | ||||
|   #   http://example.com/        => / | ||||
|   #   http://example.com/mublog/ => /mublog/ | ||||
|   #   https://social.example.com/        => / | ||||
|   #   https://example.com/social/ => /social/ | ||||
|   # | ||||
|   RewriteBase / | ||||
|   #RewriteBase /mublog/ | ||||
| @@ -26,7 +46,7 @@ | ||||
|  | ||||
| <FilesMatch "\.(ini)"> | ||||
|   # For mod_access_compat in Apache <2.4 | ||||
|   Order allow,deny | ||||
|   #Order allow,deny | ||||
|  | ||||
|   # Use this instead for Apache >2.4 (mod_authz_host) | ||||
|   # Require all denied | ||||
|   | ||||
| @@ -265,7 +265,7 @@ function main() | ||||
|     $site_ssl = common_config('site', 'ssl'); | ||||
|  | ||||
|     // If the request is HTTP and it should be HTTPS... | ||||
|     if ($site_ssl != 'never' && !StatusNet::isHTTPS() && common_is_sensitive($args['action'])) { | ||||
|     if ($site_ssl != 'never' && !GNUsocial::isHTTPS() && common_is_sensitive($args['action'])) { | ||||
|         common_redirect(common_local_url($args['action'], $args)); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -265,10 +265,10 @@ class WebInstaller extends Installer | ||||
|                     <li> | ||||
|                         <label for="site_profile">Type of site</label> | ||||
|                         <select id="site_profile" name="site_profile"> | ||||
|                             <option value="private">Private</option> | ||||
|                             <option value="community">Community</option> | ||||
|                             <option value ="public">Public</option> | ||||
|                             <option value ="singleuser">Single User</option> | ||||
|                             <option value="public">Public (open registration)</option> | ||||
|                             <option value="singleuser">Single User</option> | ||||
|                             <option value="private">Private (no federation)</option> | ||||
|                         </select> | ||||
|                         <p class="form_guide">Initial access settings for your site</p> | ||||
|                     </li> | ||||
|   | ||||
| Before Width: | Height: | Size: 1.7 KiB | 
| Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B | 
| Before Width: | Height: | Size: 208 B After Width: | Height: | Size: 208 B | 
| Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 335 B | 
| Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B | 
| Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B | 
| Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 262 B | 
| Before Width: | Height: | Size: 332 B After Width: | Height: | Size: 332 B | 
| Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 280 B | 
| Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB | 
| Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB | 
| Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB | 
| Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB | 
| Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB | 
							
								
								
									
										202
									
								
								js/extlib/jquery-ui/css/smoothness/jquery-ui.css
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,8 +1,8 @@ | ||||
| /*! jQuery UI - v1.10.3 - 2013-09-12 | ||||
| /*! jQuery UI - v1.11.3 - 2015-03-07 | ||||
| * http://jqueryui.com | ||||
| * Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css | ||||
| * Includes: core.css, draggable.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, menu.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css | ||||
| * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px | ||||
| * Copyright 2013 jQuery Foundation and other contributors; Licensed MIT */ | ||||
| * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ | ||||
|  | ||||
| /* Layout helpers | ||||
| ----------------------------------*/ | ||||
| @@ -48,7 +48,7 @@ | ||||
| 	left: 0; | ||||
| 	position: absolute; | ||||
| 	opacity: 0; | ||||
| 	filter:Alpha(Opacity=0); | ||||
| 	filter:Alpha(Opacity=0); /* support: IE8 */ | ||||
| } | ||||
|  | ||||
| .ui-front { | ||||
| @@ -86,6 +86,10 @@ | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| } | ||||
| .ui-draggable-handle { | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .ui-resizable { | ||||
| 	position: relative; | ||||
| } | ||||
| @@ -93,6 +97,8 @@ | ||||
| 	position: absolute; | ||||
| 	font-size: 0.1px; | ||||
| 	display: block; | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .ui-resizable-disabled .ui-resizable-handle, | ||||
| .ui-resizable-autohide .ui-resizable-handle { | ||||
| @@ -154,25 +160,31 @@ | ||||
| 	right: -5px; | ||||
| 	top: -5px; | ||||
| } | ||||
| .ui-selectable { | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .ui-selectable-helper { | ||||
| 	position: absolute; | ||||
| 	z-index: 100; | ||||
| 	border: 1px dotted black; | ||||
| } | ||||
| .ui-sortable-handle { | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .ui-accordion .ui-accordion-header { | ||||
| 	display: block; | ||||
| 	cursor: pointer; | ||||
| 	position: relative; | ||||
| 	margin-top: 2px; | ||||
| 	margin: 2px 0 0 0; | ||||
| 	padding: .5em .5em .5em .7em; | ||||
| 	min-height: 0; /* support: IE7 */ | ||||
| 	font-size: 100%; | ||||
| } | ||||
| .ui-accordion .ui-accordion-icons { | ||||
| 	padding-left: 2.2em; | ||||
| } | ||||
| .ui-accordion .ui-accordion-noicons { | ||||
| 	padding-left: .7em; | ||||
| } | ||||
| .ui-accordion .ui-accordion-icons .ui-accordion-icons { | ||||
| 	padding-left: 2.2em; | ||||
| } | ||||
| @@ -347,12 +359,9 @@ button.ui-button::-moz-focus-inner { | ||||
| 	font-size: 1em; | ||||
| 	margin: 1px 0; | ||||
| } | ||||
| .ui-datepicker select.ui-datepicker-month-year { | ||||
| 	width: 100%; | ||||
| } | ||||
| .ui-datepicker select.ui-datepicker-month, | ||||
| .ui-datepicker select.ui-datepicker-year { | ||||
| 	width: 49%; | ||||
| 	width: 45%; | ||||
| } | ||||
| .ui-datepicker table { | ||||
| 	width: 100%; | ||||
| @@ -466,6 +475,7 @@ button.ui-button::-moz-focus-inner { | ||||
| 	border-left-width: 1px; | ||||
| } | ||||
| .ui-dialog { | ||||
| 	overflow: hidden; | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| @@ -488,7 +498,7 @@ button.ui-button::-moz-focus-inner { | ||||
| 	position: absolute; | ||||
| 	right: .3em; | ||||
| 	top: 50%; | ||||
| 	width: 21px; | ||||
| 	width: 20px; | ||||
| 	margin: -10px 0 0 0; | ||||
| 	padding: 1px; | ||||
| 	height: 20px; | ||||
| @@ -526,72 +536,56 @@ button.ui-button::-moz-focus-inner { | ||||
| } | ||||
| .ui-menu { | ||||
| 	list-style: none; | ||||
| 	padding: 2px; | ||||
| 	padding: 0; | ||||
| 	margin: 0; | ||||
| 	display: block; | ||||
| 	outline: none; | ||||
| } | ||||
| .ui-menu .ui-menu { | ||||
| 	margin-top: -3px; | ||||
| 	position: absolute; | ||||
| } | ||||
| .ui-menu .ui-menu-item { | ||||
| 	position: relative; | ||||
| 	margin: 0; | ||||
| 	padding: 0; | ||||
| 	width: 100%; | ||||
| 	padding: 3px 1em 3px .4em; | ||||
| 	cursor: pointer; | ||||
| 	min-height: 0; /* support: IE7 */ | ||||
| 	/* support: IE10, see #8844 */ | ||||
| 	list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); | ||||
| 	list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); | ||||
| } | ||||
| .ui-menu .ui-menu-divider { | ||||
| 	margin: 5px -2px 5px -2px; | ||||
| 	margin: 5px 0; | ||||
| 	height: 0; | ||||
| 	font-size: 0; | ||||
| 	line-height: 0; | ||||
| 	border-width: 1px 0 0 0; | ||||
| } | ||||
| .ui-menu .ui-menu-item a { | ||||
| 	text-decoration: none; | ||||
| 	display: block; | ||||
| 	padding: 2px .4em; | ||||
| 	line-height: 1.5; | ||||
| 	min-height: 0; /* support: IE7 */ | ||||
| 	font-weight: normal; | ||||
| } | ||||
| .ui-menu .ui-menu-item a.ui-state-focus, | ||||
| .ui-menu .ui-menu-item a.ui-state-active { | ||||
| 	font-weight: normal; | ||||
| .ui-menu .ui-state-focus, | ||||
| .ui-menu .ui-state-active { | ||||
| 	margin: -1px; | ||||
| } | ||||
|  | ||||
| .ui-menu .ui-state-disabled { | ||||
| 	font-weight: normal; | ||||
| 	margin: .4em 0 .2em; | ||||
| 	line-height: 1.5; | ||||
| } | ||||
| .ui-menu .ui-state-disabled a { | ||||
| 	cursor: default; | ||||
| } | ||||
|  | ||||
| /* icon support */ | ||||
| .ui-menu-icons { | ||||
| 	position: relative; | ||||
| } | ||||
| .ui-menu-icons .ui-menu-item a { | ||||
| 	position: relative; | ||||
| .ui-menu-icons .ui-menu-item { | ||||
| 	padding-left: 2em; | ||||
| } | ||||
|  | ||||
| /* left-aligned */ | ||||
| .ui-menu .ui-icon { | ||||
| 	position: absolute; | ||||
| 	top: .2em; | ||||
| 	top: 0; | ||||
| 	bottom: 0; | ||||
| 	left: .2em; | ||||
| 	margin: auto 0; | ||||
| } | ||||
|  | ||||
| /* right-aligned */ | ||||
| .ui-menu .ui-menu-icon { | ||||
| 	position: static; | ||||
| 	float: right; | ||||
| 	left: auto; | ||||
| 	right: 0; | ||||
| } | ||||
| .ui-progressbar { | ||||
| 	height: 2em; | ||||
| @@ -603,14 +597,63 @@ button.ui-button::-moz-focus-inner { | ||||
| 	height: 100%; | ||||
| } | ||||
| .ui-progressbar .ui-progressbar-overlay { | ||||
| 	background: url("images/animated-overlay.gif"); | ||||
| 	background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); | ||||
| 	height: 100%; | ||||
| 	filter: alpha(opacity=25); | ||||
| 	filter: alpha(opacity=25); /* support: IE8 */ | ||||
| 	opacity: 0.25; | ||||
| } | ||||
| .ui-progressbar-indeterminate .ui-progressbar-value { | ||||
| 	background-image: none; | ||||
| } | ||||
| .ui-selectmenu-menu { | ||||
| 	padding: 0; | ||||
| 	margin: 0; | ||||
| 	position: absolute; | ||||
| 	top: 0; | ||||
| 	left: 0; | ||||
| 	display: none; | ||||
| } | ||||
| .ui-selectmenu-menu .ui-menu { | ||||
| 	overflow: auto; | ||||
| 	/* Support: IE7 */ | ||||
| 	overflow-x: hidden; | ||||
| 	padding-bottom: 1px; | ||||
| } | ||||
| .ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { | ||||
| 	font-size: 1em; | ||||
| 	font-weight: bold; | ||||
| 	line-height: 1.5; | ||||
| 	padding: 2px 0.4em; | ||||
| 	margin: 0.5em 0 0 0; | ||||
| 	height: auto; | ||||
| 	border: 0; | ||||
| } | ||||
| .ui-selectmenu-open { | ||||
| 	display: block; | ||||
| } | ||||
| .ui-selectmenu-button { | ||||
| 	display: inline-block; | ||||
| 	overflow: hidden; | ||||
| 	position: relative; | ||||
| 	text-decoration: none; | ||||
| 	cursor: pointer; | ||||
| } | ||||
| .ui-selectmenu-button span.ui-icon { | ||||
| 	right: 0.5em; | ||||
| 	left: auto; | ||||
| 	margin-top: -8px; | ||||
| 	position: absolute; | ||||
| 	top: 50%; | ||||
| } | ||||
| .ui-selectmenu-button span.ui-selectmenu-text { | ||||
| 	text-align: left; | ||||
| 	padding: 0.4em 2.1em 0.4em 1em; | ||||
| 	display: block; | ||||
| 	line-height: 1.4; | ||||
| 	overflow: hidden; | ||||
| 	text-overflow: ellipsis; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| .ui-slider { | ||||
| 	position: relative; | ||||
| 	text-align: left; | ||||
| @@ -621,6 +664,8 @@ button.ui-button::-moz-focus-inner { | ||||
| 	width: 1.2em; | ||||
| 	height: 1.2em; | ||||
| 	cursor: default; | ||||
| 	-ms-touch-action: none; | ||||
| 	touch-action: none; | ||||
| } | ||||
| .ui-slider .ui-slider-range { | ||||
| 	position: absolute; | ||||
| @@ -631,7 +676,7 @@ button.ui-button::-moz-focus-inner { | ||||
| 	background-position: 0 0; | ||||
| } | ||||
|  | ||||
| /* For IE8 - See #6727 */ | ||||
| /* support: IE8 - See #6727 */ | ||||
| .ui-slider.ui-state-disabled .ui-slider-handle, | ||||
| .ui-slider.ui-state-disabled .ui-slider-range { | ||||
| 	filter: inherit; | ||||
| @@ -704,13 +749,13 @@ button.ui-button::-moz-focus-inner { | ||||
| 	overflow: hidden; | ||||
| 	right: 0; | ||||
| } | ||||
| /* more specificity required here to overide default borders */ | ||||
| /* more specificity required here to override default borders */ | ||||
| .ui-spinner a.ui-spinner-button { | ||||
| 	border-top: none; | ||||
| 	border-bottom: none; | ||||
| 	border-right: none; | ||||
| } | ||||
| /* vertical centre icon */ | ||||
| /* vertically center icon */ | ||||
| .ui-spinner .ui-icon { | ||||
| 	position: absolute; | ||||
| 	margin-top: -8px; | ||||
| @@ -747,7 +792,7 @@ button.ui-button::-moz-focus-inner { | ||||
| 	padding: 0; | ||||
| 	white-space: nowrap; | ||||
| } | ||||
| .ui-tabs .ui-tabs-nav li a { | ||||
| .ui-tabs .ui-tabs-nav .ui-tabs-anchor { | ||||
| 	float: left; | ||||
| 	padding: .5em 1em; | ||||
| 	text-decoration: none; | ||||
| @@ -756,13 +801,12 @@ button.ui-button::-moz-focus-inner { | ||||
| 	margin-bottom: -1px; | ||||
| 	padding-bottom: 1px; | ||||
| } | ||||
| .ui-tabs .ui-tabs-nav li.ui-tabs-active a, | ||||
| .ui-tabs .ui-tabs-nav li.ui-state-disabled a, | ||||
| .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { | ||||
| .ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, | ||||
| .ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, | ||||
| .ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { | ||||
| 	cursor: text; | ||||
| } | ||||
| .ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ | ||||
| .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { | ||||
| .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { | ||||
| 	cursor: pointer; | ||||
| } | ||||
| .ui-tabs .ui-tabs-panel { | ||||
| @@ -801,7 +845,7 @@ body .ui-tooltip { | ||||
| } | ||||
| .ui-widget-content { | ||||
| 	border: 1px solid #aaaaaa; | ||||
| 	background: #ffffff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; | ||||
| 	background: #ffffff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; | ||||
| 	color: #222222; | ||||
| } | ||||
| .ui-widget-content a { | ||||
| @@ -809,7 +853,7 @@ body .ui-tooltip { | ||||
| } | ||||
| .ui-widget-header { | ||||
| 	border: 1px solid #aaaaaa; | ||||
| 	background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; | ||||
| 	background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; | ||||
| 	color: #222222; | ||||
| 	font-weight: bold; | ||||
| } | ||||
| @@ -823,7 +867,7 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-state-default, | ||||
| .ui-widget-header .ui-state-default { | ||||
| 	border: 1px solid #d3d3d3; | ||||
| 	background: #e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; | ||||
| 	background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; | ||||
| 	font-weight: normal; | ||||
| 	color: #555555; | ||||
| } | ||||
| @@ -840,14 +884,18 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-state-focus, | ||||
| .ui-widget-header .ui-state-focus { | ||||
| 	border: 1px solid #999999; | ||||
| 	background: #dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; | ||||
| 	background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; | ||||
| 	font-weight: normal; | ||||
| 	color: #212121; | ||||
| } | ||||
| .ui-state-hover a, | ||||
| .ui-state-hover a:hover, | ||||
| .ui-state-hover a:link, | ||||
| .ui-state-hover a:visited { | ||||
| .ui-state-hover a:visited, | ||||
| .ui-state-focus a, | ||||
| .ui-state-focus a:hover, | ||||
| .ui-state-focus a:link, | ||||
| .ui-state-focus a:visited { | ||||
| 	color: #212121; | ||||
| 	text-decoration: none; | ||||
| } | ||||
| @@ -855,7 +903,7 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-state-active, | ||||
| .ui-widget-header .ui-state-active { | ||||
| 	border: 1px solid #aaaaaa; | ||||
| 	background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; | ||||
| 	background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; | ||||
| 	font-weight: normal; | ||||
| 	color: #212121; | ||||
| } | ||||
| @@ -872,7 +920,7 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-state-highlight, | ||||
| .ui-widget-header .ui-state-highlight { | ||||
| 	border: 1px solid #fcefa1; | ||||
| 	background: #fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; | ||||
| 	background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; | ||||
| 	color: #363636; | ||||
| } | ||||
| .ui-state-highlight a, | ||||
| @@ -884,7 +932,7 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-state-error, | ||||
| .ui-widget-header .ui-state-error { | ||||
| 	border: 1px solid #cd0a0a; | ||||
| 	background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; | ||||
| 	background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; | ||||
| 	color: #cd0a0a; | ||||
| } | ||||
| .ui-state-error a, | ||||
| @@ -906,18 +954,18 @@ body .ui-tooltip { | ||||
| .ui-widget-content .ui-priority-secondary, | ||||
| .ui-widget-header .ui-priority-secondary { | ||||
| 	opacity: .7; | ||||
| 	filter:Alpha(Opacity=70); | ||||
| 	filter:Alpha(Opacity=70); /* support: IE8 */ | ||||
| 	font-weight: normal; | ||||
| } | ||||
| .ui-state-disabled, | ||||
| .ui-widget-content .ui-state-disabled, | ||||
| .ui-widget-header .ui-state-disabled { | ||||
| 	opacity: .35; | ||||
| 	filter:Alpha(Opacity=35); | ||||
| 	filter:Alpha(Opacity=35); /* support: IE8 */ | ||||
| 	background-image: none; | ||||
| } | ||||
| .ui-state-disabled .ui-icon { | ||||
| 	filter:Alpha(Opacity=35); /* For IE8 - See #6059 */ | ||||
| 	filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ | ||||
| } | ||||
|  | ||||
| /* Icons | ||||
| @@ -930,27 +978,27 @@ body .ui-tooltip { | ||||
| } | ||||
| .ui-icon, | ||||
| .ui-widget-content .ui-icon { | ||||
| 	background-image: url(images/ui-icons_222222_256x240.png); | ||||
| 	background-image: url("images/ui-icons_222222_256x240.png"); | ||||
| } | ||||
| .ui-widget-header .ui-icon { | ||||
| 	background-image: url(images/ui-icons_222222_256x240.png); | ||||
| 	background-image: url("images/ui-icons_222222_256x240.png"); | ||||
| } | ||||
| .ui-state-default .ui-icon { | ||||
| 	background-image: url(images/ui-icons_888888_256x240.png); | ||||
| 	background-image: url("images/ui-icons_888888_256x240.png"); | ||||
| } | ||||
| .ui-state-hover .ui-icon, | ||||
| .ui-state-focus .ui-icon { | ||||
| 	background-image: url(images/ui-icons_454545_256x240.png); | ||||
| 	background-image: url("images/ui-icons_454545_256x240.png"); | ||||
| } | ||||
| .ui-state-active .ui-icon { | ||||
| 	background-image: url(images/ui-icons_454545_256x240.png); | ||||
| 	background-image: url("images/ui-icons_454545_256x240.png"); | ||||
| } | ||||
| .ui-state-highlight .ui-icon { | ||||
| 	background-image: url(images/ui-icons_2e83ff_256x240.png); | ||||
| 	background-image: url("images/ui-icons_2e83ff_256x240.png"); | ||||
| } | ||||
| .ui-state-error .ui-icon, | ||||
| .ui-state-error-text .ui-icon { | ||||
| 	background-image: url(images/ui-icons_cd0a0a_256x240.png); | ||||
| 	background-image: url("images/ui-icons_cd0a0a_256x240.png"); | ||||
| } | ||||
|  | ||||
| /* positioning */ | ||||
| @@ -1163,15 +1211,15 @@ body .ui-tooltip { | ||||
|  | ||||
| /* Overlays */ | ||||
| .ui-widget-overlay { | ||||
| 	background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; | ||||
| 	background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; | ||||
| 	opacity: .3; | ||||
| 	filter: Alpha(Opacity=30); | ||||
| 	filter: Alpha(Opacity=30); /* support: IE8 */ | ||||
| } | ||||
| .ui-widget-shadow { | ||||
| 	margin: -8px 0 0 -8px; | ||||
| 	padding: 8px; | ||||
| 	background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; | ||||
| 	background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; | ||||
| 	opacity: .3; | ||||
| 	filter: Alpha(Opacity=30); | ||||
| 	filter: Alpha(Opacity=30); /* support: IE8 */ | ||||
| 	border-radius: 8px; | ||||
| } | ||||
|   | ||||
							
								
								
									
										6405
									
								
								js/extlib/jquery-ui/jquery-ui.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,5 +1,5 @@ | ||||
| /*! | ||||
|  * jQuery Cookie Plugin v1.3.1 | ||||
|  * jQuery Cookie Plugin v1.4.1 | ||||
|  * https://github.com/carhartl/jquery-cookie | ||||
|  * | ||||
|  * Copyright 2013 Klaus Hartl | ||||
| @@ -7,57 +7,65 @@ | ||||
|  */ | ||||
| (function (factory) { | ||||
| 	if (typeof define === 'function' && define.amd) { | ||||
| 		// AMD. Register as anonymous module. | ||||
| 		// AMD | ||||
| 		define(['jquery'], factory); | ||||
| 	} else if (typeof exports === 'object') { | ||||
| 		// CommonJS | ||||
| 		factory(require('jquery')); | ||||
| 	} else { | ||||
| 		// Browser globals. | ||||
| 		// Browser globals | ||||
| 		factory(jQuery); | ||||
| 	} | ||||
| }(function ($) { | ||||
|  | ||||
| 	var pluses = /\+/g; | ||||
|  | ||||
| 	function decode(s) { | ||||
| 		if (config.raw) { | ||||
| 			return s; | ||||
| 		} | ||||
| 		try { | ||||
| 			// If we can't decode the cookie, ignore it, it's unusable. | ||||
| 			return decodeURIComponent(s.replace(pluses, ' ')); | ||||
| 		} catch(e) {} | ||||
| 	function encode(s) { | ||||
| 		return config.raw ? s : encodeURIComponent(s); | ||||
| 	} | ||||
|  | ||||
| 	function decodeAndParse(s) { | ||||
| 	function decode(s) { | ||||
| 		return config.raw ? s : decodeURIComponent(s); | ||||
| 	} | ||||
|  | ||||
| 	function stringifyCookieValue(value) { | ||||
| 		return encode(config.json ? JSON.stringify(value) : String(value)); | ||||
| 	} | ||||
|  | ||||
| 	function parseCookieValue(s) { | ||||
| 		if (s.indexOf('"') === 0) { | ||||
| 			// This is a quoted cookie as according to RFC2068, unescape... | ||||
| 			s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); | ||||
| 		} | ||||
|  | ||||
| 		s = decode(s); | ||||
|  | ||||
| 		try { | ||||
| 			// Replace server-side written pluses with spaces. | ||||
| 			// If we can't decode the cookie, ignore it, it's unusable. | ||||
| 			// If we can't parse the cookie, ignore it, it's unusable. | ||||
| 			s = decodeURIComponent(s.replace(pluses, ' ')); | ||||
| 			return config.json ? JSON.parse(s) : s; | ||||
| 		} catch(e) {} | ||||
| 	} | ||||
|  | ||||
| 	function read(s, converter) { | ||||
| 		var value = config.raw ? s : parseCookieValue(s); | ||||
| 		return $.isFunction(converter) ? converter(value) : value; | ||||
| 	} | ||||
|  | ||||
| 	var config = $.cookie = function (key, value, options) { | ||||
|  | ||||
| 		// Write | ||||
| 		if (value !== undefined) { | ||||
|  | ||||
| 		if (value !== undefined && !$.isFunction(value)) { | ||||
| 			options = $.extend({}, config.defaults, options); | ||||
|  | ||||
| 			if (typeof options.expires === 'number') { | ||||
| 				var days = options.expires, t = options.expires = new Date(); | ||||
| 				t.setDate(t.getDate() + days); | ||||
| 				t.setTime(+t + days * 864e+5); | ||||
| 			} | ||||
|  | ||||
| 			value = config.json ? JSON.stringify(value) : String(value); | ||||
|  | ||||
| 			return (document.cookie = [ | ||||
| 				config.raw ? key : encodeURIComponent(key), | ||||
| 				'=', | ||||
| 				config.raw ? value : encodeURIComponent(value), | ||||
| 				encode(key), '=', stringifyCookieValue(value), | ||||
| 				options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE | ||||
| 				options.path    ? '; path=' + options.path : '', | ||||
| 				options.domain  ? '; domain=' + options.domain : '', | ||||
| @@ -80,12 +88,13 @@ | ||||
| 			var cookie = parts.join('='); | ||||
|  | ||||
| 			if (key && key === name) { | ||||
| 				result = decodeAndParse(cookie); | ||||
| 				// If second argument (value) is a function it's a converter... | ||||
| 				result = read(cookie, value); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			// Prevent storing a cookie that we couldn't decode. | ||||
| 			if (!key && (cookie = decodeAndParse(cookie)) !== undefined) { | ||||
| 			if (!key && (cookie = read(cookie)) !== undefined) { | ||||
| 				result[name] = cookie; | ||||
| 			} | ||||
| 		} | ||||
| @@ -96,12 +105,13 @@ | ||||
| 	config.defaults = {}; | ||||
|  | ||||
| 	$.removeCookie = function (key, options) { | ||||
| 		if ($.cookie(key) !== undefined) { | ||||
| 			// Must not alter options, thus extending a fresh object... | ||||
| 			$.cookie(key, '', $.extend({}, options, { expires: -1 })); | ||||
| 			return true; | ||||
| 		if ($.cookie(key) === undefined) { | ||||
| 			return false; | ||||
| 		} | ||||
| 		return false; | ||||
|  | ||||
| 		// Must not alter options, thus extending a fresh object... | ||||
| 		$.cookie(key, '', $.extend({}, options, { expires: -1 })); | ||||
| 		return !$.cookie(key); | ||||
| 	}; | ||||
|  | ||||
| })); | ||||
|   | ||||
| @@ -1,15 +1,28 @@ | ||||
| /*! | ||||
|  * jQuery Form Plugin | ||||
|  * version: 3.43.0-2013.09.03 | ||||
|  * version: 3.51.0-2014.06.20 | ||||
|  * Requires jQuery v1.5 or later | ||||
|  * Copyright (c) 2013 M. Alsup | ||||
|  * Copyright (c) 2014 M. Alsup | ||||
|  * Examples and documentation at: http://malsup.com/jquery/form/ | ||||
|  * Project repository: https://github.com/malsup/form | ||||
|  * Dual licensed under the MIT and GPL licenses. | ||||
|  * https://github.com/malsup/form#copyright-and-license | ||||
|  */ | ||||
| /*global ActiveXObject */ | ||||
| ;(function($) { | ||||
|  | ||||
| // AMD support | ||||
| (function (factory) { | ||||
|     "use strict"; | ||||
|     if (typeof define === 'function' && define.amd) { | ||||
|         // using AMD; register as anon module | ||||
|         define(['jquery'], factory); | ||||
|     } else { | ||||
|         // no AMD; invoke directly | ||||
|         factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| (function($) { | ||||
| "use strict"; | ||||
|  | ||||
| /* | ||||
| @@ -63,11 +76,13 @@ var hasProp = !!$.fn.prop; | ||||
| // contains inputs with names like "action" or "method"; in those | ||||
| // cases "prop" returns the element | ||||
| $.fn.attr2 = function() { | ||||
|     if ( ! hasProp ) | ||||
|     if ( ! hasProp ) { | ||||
|         return this.attr.apply(this, arguments); | ||||
|     } | ||||
|     var val = this.prop.apply(this, arguments); | ||||
|     if ( ( val && val.jquery ) || typeof val === 'string' ) | ||||
|     if ( ( val && val.jquery ) || typeof val === 'string' ) { | ||||
|         return val; | ||||
|     } | ||||
|     return this.attr.apply(this, arguments); | ||||
| }; | ||||
|  | ||||
| @@ -209,7 +224,7 @@ $.fn.ajaxSubmit = function(options) { | ||||
|  | ||||
|     // [value] (issue #113), also see comment: | ||||
|     // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219 | ||||
|     var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() != ''; }); | ||||
|     var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() !== ''; }); | ||||
|  | ||||
|     var hasFileInputs = fileInputs.length > 0; | ||||
|     var mp = 'multipart/form-data'; | ||||
| @@ -245,8 +260,9 @@ $.fn.ajaxSubmit = function(options) { | ||||
|     $form.removeData('jqxhr').data('jqxhr', jqxhr); | ||||
|  | ||||
|     // clear element array | ||||
|     for (var k=0; k < elements.length; k++) | ||||
|     for (var k=0; k < elements.length; k++) { | ||||
|         elements[k] = null; | ||||
|     } | ||||
|  | ||||
|     // fire 'notify' event | ||||
|     this.trigger('form-submit-notify', [this, options]); | ||||
| @@ -278,9 +294,11 @@ $.fn.ajaxSubmit = function(options) { | ||||
|  | ||||
|         if (options.extraData) { | ||||
|             var serializedData = deepSerialize(options.extraData); | ||||
|             for (i=0; i < serializedData.length; i++) | ||||
|                 if (serializedData[i]) | ||||
|             for (i=0; i < serializedData.length; i++) { | ||||
|                 if (serializedData[i]) { | ||||
|                     formdata.append(serializedData[i][0], serializedData[i][1]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         options.data = null; | ||||
| @@ -312,11 +330,18 @@ $.fn.ajaxSubmit = function(options) { | ||||
|         } | ||||
|  | ||||
|         s.data = null; | ||||
|             var beforeSend = s.beforeSend; | ||||
|             s.beforeSend = function(xhr, o) { | ||||
|         var beforeSend = s.beforeSend; | ||||
|         s.beforeSend = function(xhr, o) { | ||||
|             //Send FormData() provided by user | ||||
|             if (options.formData) { | ||||
|                 o.data = options.formData; | ||||
|             } | ||||
|             else { | ||||
|                 o.data = formdata; | ||||
|                 if(beforeSend) | ||||
|                     beforeSend.call(this, xhr, o); | ||||
|             } | ||||
|             if(beforeSend) { | ||||
|                 beforeSend.call(this, xhr, o); | ||||
|             } | ||||
|         }; | ||||
|         return $.ajax(s); | ||||
|     } | ||||
| @@ -335,10 +360,12 @@ $.fn.ajaxSubmit = function(options) { | ||||
|             // ensure that every serialized input is still enabled | ||||
|             for (i=0; i < elements.length; i++) { | ||||
|                 el = $(elements[i]); | ||||
|                 if ( hasProp ) | ||||
|                 if ( hasProp ) { | ||||
|                     el.prop('disabled', false); | ||||
|                 else | ||||
|                 } | ||||
|                 else { | ||||
|                     el.removeAttr('disabled'); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -348,10 +375,12 @@ $.fn.ajaxSubmit = function(options) { | ||||
|         if (s.iframeTarget) { | ||||
|             $io = $(s.iframeTarget); | ||||
|             n = $io.attr2('name'); | ||||
|             if (!n) | ||||
|                  $io.attr2('name', id); | ||||
|             else | ||||
|             if (!n) { | ||||
|                 $io.attr2('name', id); | ||||
|             } | ||||
|             else { | ||||
|                 id = n; | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />'); | ||||
| @@ -383,12 +412,15 @@ $.fn.ajaxSubmit = function(options) { | ||||
|  | ||||
|                 $io.attr('src', s.iframeSrc); // abort op in progress | ||||
|                 xhr.error = e; | ||||
|                 if (s.error) | ||||
|                 if (s.error) { | ||||
|                     s.error.call(s.context, xhr, e, status); | ||||
|                 if (g) | ||||
|                 } | ||||
|                 if (g) { | ||||
|                     $.event.trigger("ajaxError", [xhr, s, e]); | ||||
|                 if (s.complete) | ||||
|                 } | ||||
|                 if (s.complete) { | ||||
|                     s.complete.call(s.context, xhr, e); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -475,7 +507,10 @@ $.fn.ajaxSubmit = function(options) { | ||||
|         // take a breath so that pending repaints get some cpu time before the upload starts | ||||
|         function doSubmit() { | ||||
|             // make sure form attrs are set | ||||
|             var t = $form.attr2('target'), a = $form.attr2('action'); | ||||
|             var t = $form.attr2('target'),  | ||||
|                 a = $form.attr2('action'),  | ||||
|                 mp = 'multipart/form-data', | ||||
|                 et = $form.attr('enctype') || $form.attr('encoding') || mp; | ||||
|  | ||||
|             // update form attrs in IE friendly way | ||||
|             form.setAttribute('target',id); | ||||
| @@ -504,14 +539,16 @@ $.fn.ajaxSubmit = function(options) { | ||||
|                 try { | ||||
|                     var state = getDoc(io).readyState; | ||||
|                     log('state = ' + state); | ||||
|                     if (state && state.toLowerCase() == 'uninitialized') | ||||
|                     if (state && state.toLowerCase() == 'uninitialized') { | ||||
|                         setTimeout(checkState,50); | ||||
|                     } | ||||
|                 } | ||||
|                 catch(e) { | ||||
|                     log('Server abort: ' , e, ' (', e.name, ')'); | ||||
|                     cb(SERVER_ABORT); | ||||
|                     if (timeoutHandle) | ||||
|                     if (timeoutHandle) { | ||||
|                         clearTimeout(timeoutHandle); | ||||
|                     } | ||||
|                     timeoutHandle = undefined; | ||||
|                 } | ||||
|             } | ||||
| @@ -540,10 +577,12 @@ $.fn.ajaxSubmit = function(options) { | ||||
|                     // add iframe to doc and submit the form | ||||
|                     $io.appendTo('body'); | ||||
|                 } | ||||
|                 if (io.attachEvent) | ||||
|                 if (io.attachEvent) { | ||||
|                     io.attachEvent('onload', cb); | ||||
|                 else | ||||
|                 } | ||||
|                 else { | ||||
|                     io.addEventListener('load', cb, false); | ||||
|                 } | ||||
|                 setTimeout(checkState,15); | ||||
|  | ||||
|                 try { | ||||
| @@ -557,6 +596,7 @@ $.fn.ajaxSubmit = function(options) { | ||||
|             finally { | ||||
|                 // reset attrs and remove "extra" input elements | ||||
|                 form.setAttribute('action',a); | ||||
|                 form.setAttribute('enctype', et); // #380 | ||||
|                 if(t) { | ||||
|                     form.setAttribute('target', t); | ||||
|                 } else { | ||||
| @@ -598,13 +638,16 @@ $.fn.ajaxSubmit = function(options) { | ||||
|  | ||||
|             if (!doc || doc.location.href == s.iframeSrc) { | ||||
|                 // response not received yet | ||||
|                 if (!timedOut) | ||||
|                 if (!timedOut) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             if (io.detachEvent) | ||||
|             if (io.detachEvent) { | ||||
|                 io.detachEvent('onload', cb); | ||||
|             else | ||||
|             } | ||||
|             else { | ||||
|                 io.removeEventListener('load', cb, false); | ||||
|             } | ||||
|  | ||||
|             var status = 'success', errMsg; | ||||
|             try { | ||||
| @@ -631,8 +674,9 @@ $.fn.ajaxSubmit = function(options) { | ||||
|                 var docRoot = doc.body ? doc.body : doc.documentElement; | ||||
|                 xhr.responseText = docRoot ? docRoot.innerHTML : null; | ||||
|                 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc; | ||||
|                 if (isXml) | ||||
|                 if (isXml) { | ||||
|                     s.dataType = 'xml'; | ||||
|                 } | ||||
|                 xhr.getResponseHeader = function(header){ | ||||
|                     var headers = {'content-type': s.dataType}; | ||||
|                     return headers[header.toLowerCase()]; | ||||
| @@ -695,42 +739,52 @@ $.fn.ajaxSubmit = function(options) { | ||||
|  | ||||
|             // ordering of these callbacks/triggers is odd, but that's how $.ajax does it | ||||
|             if (status === 'success') { | ||||
|                 if (s.success) | ||||
|                 if (s.success) { | ||||
|                     s.success.call(s.context, data, 'success', xhr); | ||||
|                 } | ||||
|                 deferred.resolve(xhr.responseText, 'success', xhr); | ||||
|                 if (g) | ||||
|                 if (g) { | ||||
|                     $.event.trigger("ajaxSuccess", [xhr, s]); | ||||
|                 } | ||||
|             } | ||||
|             else if (status) { | ||||
|                 if (errMsg === undefined) | ||||
|                 if (errMsg === undefined) { | ||||
|                     errMsg = xhr.statusText; | ||||
|                 if (s.error) | ||||
|                 } | ||||
|                 if (s.error) { | ||||
|                     s.error.call(s.context, xhr, status, errMsg); | ||||
|                 } | ||||
|                 deferred.reject(xhr, 'error', errMsg); | ||||
|                 if (g) | ||||
|                 if (g) { | ||||
|                     $.event.trigger("ajaxError", [xhr, s, errMsg]); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (g) | ||||
|             if (g) { | ||||
|                 $.event.trigger("ajaxComplete", [xhr, s]); | ||||
|             } | ||||
|  | ||||
|             if (g && ! --$.active) { | ||||
|                 $.event.trigger("ajaxStop"); | ||||
|             } | ||||
|  | ||||
|             if (s.complete) | ||||
|             if (s.complete) { | ||||
|                 s.complete.call(s.context, xhr, status); | ||||
|             } | ||||
|  | ||||
|             callbackProcessed = true; | ||||
|             if (s.timeout) | ||||
|             if (s.timeout) { | ||||
|                 clearTimeout(timeoutHandle); | ||||
|             } | ||||
|  | ||||
|             // clean up | ||||
|             setTimeout(function() { | ||||
|                 if (!s.iframeTarget) | ||||
|                 if (!s.iframeTarget) { | ||||
|                     $io.remove(); | ||||
|                 else  //adding else to clean up existing iframe response. | ||||
|                 } | ||||
|                 else { //adding else to clean up existing iframe response. | ||||
|                     $io.attr('src', s.iframeSrc); | ||||
|                 } | ||||
|                 xhr.responseXML = null; | ||||
|             }, 100); | ||||
|         } | ||||
| @@ -758,8 +812,9 @@ $.fn.ajaxSubmit = function(options) { | ||||
|                 data = xml ? xhr.responseXML : xhr.responseText; | ||||
|  | ||||
|             if (xml && data.documentElement.nodeName === 'parsererror') { | ||||
|                 if ($.error) | ||||
|                 if ($.error) { | ||||
|                     $.error('parsererror'); | ||||
|                 } | ||||
|             } | ||||
|             if (s && s.dataFilter) { | ||||
|                 data = s.dataFilter(data, type); | ||||
| @@ -832,7 +887,7 @@ function doAjaxSubmit(e) { | ||||
|     var options = e.data; | ||||
|     if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed | ||||
|         e.preventDefault(); | ||||
|         $(this).ajaxSubmit(options); | ||||
|         $(e.target).ajaxSubmit(options); // #365 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -891,8 +946,23 @@ $.fn.formToArray = function(semantic, elements) { | ||||
|     } | ||||
|  | ||||
|     var form = this[0]; | ||||
|     var formId = this.attr('id'); | ||||
|     var els = semantic ? form.getElementsByTagName('*') : form.elements; | ||||
|     if (!els) { | ||||
|     var els2; | ||||
|  | ||||
|     if (els && !/MSIE [678]/.test(navigator.userAgent)) { // #390 | ||||
|         els = $(els).get();  // convert to standard array | ||||
|     } | ||||
|  | ||||
|     // #386; account for inputs outside the form which use the 'form' attribute | ||||
|     if ( formId ) { | ||||
|         els2 = $(':input[form="' + formId + '"]').get(); // hat tip @thet | ||||
|         if ( els2.length ) { | ||||
|             els = (els || []).concat(els2); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (!els || !els.length) { | ||||
|         return a; | ||||
|     } | ||||
|  | ||||
| @@ -915,15 +985,17 @@ $.fn.formToArray = function(semantic, elements) { | ||||
|  | ||||
|         v = $.fieldValue(el, true); | ||||
|         if (v && v.constructor == Array) { | ||||
|             if (elements) | ||||
|             if (elements) { | ||||
|                 elements.push(el); | ||||
|             } | ||||
|             for(j=0, jmax=v.length; j < jmax; j++) { | ||||
|                 a.push({name: n, value: v[j]}); | ||||
|             } | ||||
|         } | ||||
|         else if (feature.fileapi && el.type == 'file') { | ||||
|             if (elements) | ||||
|             if (elements) { | ||||
|                 elements.push(el); | ||||
|             } | ||||
|             var files = el.files; | ||||
|             if (files.length) { | ||||
|                 for (j=0; j < files.length; j++) { | ||||
| @@ -936,8 +1008,9 @@ $.fn.formToArray = function(semantic, elements) { | ||||
|             } | ||||
|         } | ||||
|         else if (v !== null && typeof v != 'undefined') { | ||||
|             if (elements) | ||||
|             if (elements) { | ||||
|                 elements.push(el); | ||||
|             } | ||||
|             a.push({name: n, value: v, type: el.type, required: el.required}); | ||||
|         } | ||||
|     } | ||||
| @@ -1033,10 +1106,12 @@ $.fn.fieldValue = function(successful) { | ||||
|         if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) { | ||||
|             continue; | ||||
|         } | ||||
|         if (v.constructor == Array) | ||||
|         if (v.constructor == Array) { | ||||
|             $.merge(val, v); | ||||
|         else | ||||
|         } | ||||
|         else { | ||||
|             val.push(v); | ||||
|         } | ||||
|     } | ||||
|     return val; | ||||
| }; | ||||
| @@ -1070,7 +1145,7 @@ $.fieldValue = function(el, successful) { | ||||
|             if (op.selected) { | ||||
|                 var v = op.value; | ||||
|                 if (!v) { // extra pain for IE... | ||||
|                     v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value; | ||||
|                     v = (op.attributes && op.attributes.value && !(op.attributes.value.specified)) ? op.text : op.value; | ||||
|                 } | ||||
|                 if (one) { | ||||
|                     return v; | ||||
| @@ -1113,21 +1188,22 @@ $.fn.clearFields = $.fn.clearInputs = function(includeHidden) { | ||||
|         else if (tag == 'select') { | ||||
|             this.selectedIndex = -1; | ||||
|         } | ||||
| 		else if (t == "file") { | ||||
| 			if (/MSIE/.test(navigator.userAgent)) { | ||||
| 				$(this).replaceWith($(this).clone(true)); | ||||
| 			} else { | ||||
| 				$(this).val(''); | ||||
| 			} | ||||
| 		} | ||||
|         else if (t == "file") { | ||||
|             if (/MSIE/.test(navigator.userAgent)) { | ||||
|                 $(this).replaceWith($(this).clone(true)); | ||||
|             } else { | ||||
|                 $(this).val(''); | ||||
|             } | ||||
|         } | ||||
|         else if (includeHidden) { | ||||
|             // includeHidden can be the value true, or it can be a selector string | ||||
|             // indicating a special test; for example: | ||||
|             //  $('#myForm').clearForm('.special:hidden') | ||||
|             // the above would clean hidden inputs that have the class of 'special' | ||||
|             if ( (includeHidden === true && /hidden/.test(t)) || | ||||
|                  (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) | ||||
|                  (typeof includeHidden == 'string' && $(this).is(includeHidden)) ) { | ||||
|                 this.value = ''; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| }; | ||||
| @@ -1186,8 +1262,9 @@ $.fn.ajaxSubmit.debug = false; | ||||
|  | ||||
| // helper fn for console logging | ||||
| function log() { | ||||
|     if (!$.fn.ajaxSubmit.debug) | ||||
|     if (!$.fn.ajaxSubmit.debug) { | ||||
|         return; | ||||
|     } | ||||
|     var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,''); | ||||
|     if (window.console && window.console.log) { | ||||
|         window.console.log(msg); | ||||
| @@ -1197,4 +1274,4 @@ function log() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| })( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto ); | ||||
| })); | ||||
|   | ||||
| @@ -1,168 +0,0 @@ | ||||
| /** | ||||
|  * @license In-Field Label jQuery Plugin | ||||
|  * http://fuelyourcoding.com/scripts/infield.html | ||||
|  * http://github.com/streetpc/jquery-infieldlabels | ||||
|  * | ||||
|  * Copyright (c) 2009 Doug Neiner, Adrien Lavoillotte | ||||
|  * Dual licensed under the MIT and GPL licenses, see: | ||||
|  * http://docs.jquery.com/License | ||||
|  * | ||||
|  * @version 0.2.1 | ||||
|  */ | ||||
| (function ($) { | ||||
|  | ||||
|   // private constants | ||||
|   //  - states | ||||
|   var BLUR = 0, // field is empty & unfocused | ||||
|       FOCUS = 1, // field is empty & focused | ||||
|       NOT_EMPTY = 2, // field is not empty | ||||
|   //  - accepted input type  | ||||
|       INPUT_TYPE = /^(?:text|password|search|number|tel|url|email|date(?:time(?:-local)?)?|time|month|week)?$/, | ||||
|   //  - state transitions | ||||
|       T = function(from, to) { return (from << 3) | to; }, | ||||
|       TRANSITIONS = {}; | ||||
|    | ||||
|   // init transitions | ||||
|   TRANSITIONS[T( FOCUS, BLUR )]      = function(base) { base.fadeTo(1.0); }; | ||||
|   TRANSITIONS[T( NOT_EMPTY, BLUR )]  = function(base) { base.$label.css({opacity: 1.0}).show(); base.emptied(true); }; | ||||
|   TRANSITIONS[T( BLUR, FOCUS )]      = function(base) { base.fadeTo(base.options.fadeOpacity); }; | ||||
|   TRANSITIONS[T( NOT_EMPTY, FOCUS )] = function(base) { base.$label.css({opacity: base.options.fadeOpacity}).show(); base.emptied(true); }; | ||||
|   TRANSITIONS[T( BLUR, NOT_EMPTY )]  = function(base) { base.$label.hide(); base.emptied(false); }; | ||||
|   TRANSITIONS[T( FOCUS, NOT_EMPTY )] = TRANSITIONS[T( BLUR, NOT_EMPTY )]; | ||||
|  | ||||
|   $.InFieldLabels = function (label, field, options) { | ||||
|     // To avoid scope issues, use 'base' instead of 'this' | ||||
|     // to reference this class from internal events and functions. | ||||
|     var base = this; | ||||
|    | ||||
|     // Access to jQuery and DOM versions of each element | ||||
|     base.$label = $(label); | ||||
|     base.label  = label; | ||||
|  | ||||
|     base.$field = $(field); | ||||
|     base.field  = field; | ||||
|  | ||||
|     base.$label.data('InFieldLabels', base); | ||||
|     base.state = BLUR; | ||||
|  | ||||
|     base.init = function () { | ||||
|       // Merge supplied options with default options | ||||
|       base.options = $.extend({}, $.InFieldLabels.defaultOptions, options); | ||||
|  | ||||
|       if (base.options.labelClass) { | ||||
|         base.$label.addClass(base.options.labelClass); | ||||
|       } | ||||
|       if (base.options.disableAutocomplete) { | ||||
|         base.$field.attr('autocomplete', 'off'); | ||||
|       } | ||||
|  | ||||
|       base.$field | ||||
|           .bind('blur focus change keyup.infield cut', base.updateState) | ||||
|           // paste cannot be empty | ||||
|           .bind('paste', function(e){ base.setState(NOT_EMPTY); }); | ||||
|        | ||||
|       base.updateState(); | ||||
|     }; | ||||
|  | ||||
|     base.emptied = function(empty) { | ||||
|       if (!base.options.emptyWatch) { | ||||
|         if (empty) { | ||||
|           // namespace ensures we unbind only our handler | ||||
|           base.$field.bind('keyup.infield', base.updateState); | ||||
|         } else { | ||||
|           // save CPU but won't detect empty until blur | ||||
|           base.$field.unbind('keyup.infield', base.updateState); | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     base.fadeTo = function (opacity) { | ||||
|       if (!base.options.fadeDuration) { | ||||
|         base.$label.css({ opacity: opacity }); | ||||
|       } else { | ||||
|         base.$label.stop().animate({ opacity: opacity }, base.options.fadeDuration); | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     base.updateState = function (e, nl) { | ||||
|       var state = NOT_EMPTY; | ||||
|       if (base.field.value === '') { | ||||
|         var focus = e && e.type; | ||||
|         if (focus === 'focus' || focus === 'keyup') { | ||||
|           focus = true; | ||||
|         } else if (focus === 'blur' || focus === 'change') { | ||||
|           focus = false; | ||||
|         } else {  // last resort because slowest | ||||
|           focus = base.$field.is(':focus'); | ||||
|         } | ||||
|         state = focus ? FOCUS : BLUR; | ||||
|       } | ||||
|       base.setState(state, nl); | ||||
|     }; | ||||
|  | ||||
|     base.setState = function (state, nl) { | ||||
|       if (state === base.state) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       var transition = TRANSITIONS[T(base.state, state)]; | ||||
|       if (typeof transition === 'function') { | ||||
|         transition(base); | ||||
|         base.state = state; | ||||
|       } else { // unkown transition - shouldn't happen | ||||
|         // nl avoids looping | ||||
|         nl || base.updateState(null, true); | ||||
|       } | ||||
|     }; | ||||
|  | ||||
|     // Run the initialization method | ||||
|     base.init(); | ||||
|   }; | ||||
|  | ||||
|   $.InFieldLabels.defaultOptions = { | ||||
|     emptyWatch: true, // Keep watching the field as the user types (slower but brings back the label immediately when the field is emptied) | ||||
|     disableAutocomplete: true, // Disable autocomplete on the matched fields | ||||
|     fadeOpacity: 0.5, // Once a field has focus, how transparent should the label be | ||||
|     fadeDuration: 300, // How long should it take to animate from 1.0 opacity to the fadeOpacity | ||||
|     labelClass: 'in-field' // CSS class to apply to the label when it gets in-field | ||||
|   }; | ||||
|  | ||||
|  | ||||
|   $.fn.inFieldLabels = function (options) { | ||||
|     return this.each(function () { | ||||
|       if (this.tagName !== 'LABEL') { | ||||
|         return; | ||||
|       } | ||||
|       // Find input or textarea based on for= attribute | ||||
|       // The for attribute on the label must contain the ID | ||||
|       // of the input or textarea element | ||||
|       var for_attr = this.getAttribute('for') || this.htmlFor, | ||||
|           field, valid = true; | ||||
|       if (!for_attr) { | ||||
|         return; // Nothing to attach, since the for field wasn't used | ||||
|       } | ||||
|  | ||||
|       // Find the referenced input or textarea element | ||||
|       field = document.getElementById(for_attr); | ||||
|       if (!field) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (field.tagName === 'INPUT') { | ||||
|         valid = INPUT_TYPE.test(field.type.toLowerCase()); | ||||
|       } else if (field.tagName !== 'TEXTAREA') { | ||||
|         valid = false; | ||||
|       } | ||||
|        | ||||
|       valid = valid && !field.getAttribute('placeholder'); | ||||
|  | ||||
|       if (!valid) { | ||||
|         return; // Again, nothing to attach | ||||
|       }  | ||||
|  | ||||
|       // Only create object for input[text], input[password], or textarea | ||||
|       (new $.InFieldLabels(this, field, options)); | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
| }(jQuery)); | ||||
							
								
								
									
										7890
									
								
								js/extlib/jquery.js
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|     json2.js | ||||
|     2013-05-26 | ||||
|     2015-02-25 | ||||
|  | ||||
|     Public Domain. | ||||
|  | ||||
| @@ -48,7 +48,9 @@ | ||||
|                 Date.prototype.toJSON = function (key) { | ||||
|                     function f(n) { | ||||
|                         // Format integers to have at least two digits. | ||||
|                         return n < 10 ? '0' + n : n; | ||||
|                         return n < 10  | ||||
|                         ? '0' + n  | ||||
|                         : n; | ||||
|                     } | ||||
|  | ||||
|                     return this.getUTCFullYear()   + '-' + | ||||
| @@ -146,10 +148,12 @@ | ||||
|     redistribute. | ||||
| */ | ||||
|  | ||||
| /*jslint evil: true, regexp: true */ | ||||
| /*jslint  | ||||
|     eval, for, this  | ||||
| */ | ||||
|  | ||||
| /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, | ||||
|     call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, | ||||
| /*property | ||||
|     JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, | ||||
|     getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, | ||||
|     lastIndex, length, parse, prototype, push, replace, slice, stringify, | ||||
|     test, toJSON, toString, valueOf | ||||
| @@ -168,7 +172,13 @@ if (typeof JSON !== 'object') { | ||||
|  | ||||
|     function f(n) { | ||||
|         // Format integers to have at least two digits. | ||||
|         return n < 10 ? '0' + n : n; | ||||
|         return n < 10  | ||||
|         ? '0' + n  | ||||
|         : n; | ||||
|     } | ||||
|      | ||||
|     function this_value() { | ||||
|         return this.valueOf(); | ||||
|     } | ||||
|  | ||||
|     if (typeof Date.prototype.toJSON !== 'function') { | ||||
| @@ -176,35 +186,25 @@ if (typeof JSON !== 'object') { | ||||
|         Date.prototype.toJSON = function () { | ||||
|  | ||||
|             return isFinite(this.valueOf()) | ||||
|                 ? this.getUTCFullYear()     + '-' + | ||||
|             ? this.getUTCFullYear() + '-' + | ||||
|                     f(this.getUTCMonth() + 1) + '-' + | ||||
|                     f(this.getUTCDate())      + 'T' + | ||||
|                     f(this.getUTCHours())     + ':' + | ||||
|                     f(this.getUTCMinutes())   + ':' + | ||||
|                     f(this.getUTCSeconds())   + 'Z' | ||||
|                 : null; | ||||
|                     f(this.getUTCDate()) + 'T' + | ||||
|                     f(this.getUTCHours()) + ':' + | ||||
|                     f(this.getUTCMinutes()) + ':' + | ||||
|                     f(this.getUTCSeconds()) + 'Z' | ||||
|             : null; | ||||
|         }; | ||||
|  | ||||
|         String.prototype.toJSON      = | ||||
|             Number.prototype.toJSON  = | ||||
|             Boolean.prototype.toJSON = function () { | ||||
|                 return this.valueOf(); | ||||
|             }; | ||||
|         Boolean.prototype.toJSON = this_value; | ||||
|         Number.prototype.toJSON = this_value; | ||||
|         String.prototype.toJSON = this_value; | ||||
|     } | ||||
|  | ||||
|     var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | ||||
|         escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, | ||||
|     var cx, | ||||
|         escapable, | ||||
|         gap, | ||||
|         indent, | ||||
|         meta = {    // table of character substitutions | ||||
|             '\b': '\\b', | ||||
|             '\t': '\\t', | ||||
|             '\n': '\\n', | ||||
|             '\f': '\\f', | ||||
|             '\r': '\\r', | ||||
|             '"' : '\\"', | ||||
|             '\\': '\\\\' | ||||
|         }, | ||||
|         meta, | ||||
|         rep; | ||||
|  | ||||
|  | ||||
| @@ -216,12 +216,14 @@ if (typeof JSON !== 'object') { | ||||
| // sequences. | ||||
|  | ||||
|         escapable.lastIndex = 0; | ||||
|         return escapable.test(string) ? '"' + string.replace(escapable, function (a) { | ||||
|         return escapable.test(string)  | ||||
|         ? '"' + string.replace(escapable, function (a) { | ||||
|             var c = meta[a]; | ||||
|             return typeof c === 'string' | ||||
|                 ? c | ||||
|                 : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|         }) + '"' : '"' + string + '"'; | ||||
|             ? c | ||||
|             : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|         }) + '"'  | ||||
|         : '"' + string + '"'; | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -261,7 +263,9 @@ if (typeof JSON !== 'object') { | ||||
|  | ||||
| // JSON numbers must be finite. Encode non-finite numbers as null. | ||||
|  | ||||
|             return isFinite(value) ? String(value) : 'null'; | ||||
|             return isFinite(value)  | ||||
|             ? String(value)  | ||||
|             : 'null'; | ||||
|  | ||||
|         case 'boolean': | ||||
|         case 'null': | ||||
| @@ -305,10 +309,10 @@ if (typeof JSON !== 'object') { | ||||
| // brackets. | ||||
|  | ||||
|                 v = partial.length === 0 | ||||
|                     ? '[]' | ||||
|                     : gap | ||||
|                     ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' | ||||
|                     : '[' + partial.join(',') + ']'; | ||||
|                 ? '[]' | ||||
|                 : gap | ||||
|                 ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' | ||||
|                 : '[' + partial.join(',') + ']'; | ||||
|                 gap = mind; | ||||
|                 return v; | ||||
|             } | ||||
| @@ -322,7 +326,11 @@ if (typeof JSON !== 'object') { | ||||
|                         k = rep[i]; | ||||
|                         v = str(k, value); | ||||
|                         if (v) { | ||||
|                             partial.push(quote(k) + (gap ? ': ' : ':') + v); | ||||
|                             partial.push(quote(k) + ( | ||||
|                                 gap  | ||||
|                                 ? ': '  | ||||
|                                 : ':' | ||||
|                             ) + v); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -334,7 +342,11 @@ if (typeof JSON !== 'object') { | ||||
|                     if (Object.prototype.hasOwnProperty.call(value, k)) { | ||||
|                         v = str(k, value); | ||||
|                         if (v) { | ||||
|                             partial.push(quote(k) + (gap ? ': ' : ':') + v); | ||||
|                             partial.push(quote(k) + ( | ||||
|                                 gap  | ||||
|                                 ? ': '  | ||||
|                                 : ':' | ||||
|                             ) + v); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -344,10 +356,10 @@ if (typeof JSON !== 'object') { | ||||
| // and wrap them in braces. | ||||
|  | ||||
|             v = partial.length === 0 | ||||
|                 ? '{}' | ||||
|                 : gap | ||||
|                 ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' | ||||
|                 : '{' + partial.join(',') + '}'; | ||||
|             ? '{}' | ||||
|             : gap | ||||
|             ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' | ||||
|             : '{' + partial.join(',') + '}'; | ||||
|             gap = mind; | ||||
|             return v; | ||||
|         } | ||||
| @@ -356,6 +368,16 @@ if (typeof JSON !== 'object') { | ||||
| // If the JSON object does not yet have a stringify method, give it one. | ||||
|  | ||||
|     if (typeof JSON.stringify !== 'function') { | ||||
|         escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; | ||||
|         meta = {    // table of character substitutions | ||||
|             '\b': '\\b', | ||||
|             '\t': '\\t', | ||||
|             '\n': '\\n', | ||||
|             '\f': '\\f', | ||||
|             '\r': '\\r', | ||||
|             '"': '\\"', | ||||
|             '\\': '\\\\' | ||||
|         }; | ||||
|         JSON.stringify = function (value, replacer, space) { | ||||
|  | ||||
| // The stringify method takes a value and an optional replacer, and an optional | ||||
| @@ -403,6 +425,7 @@ if (typeof JSON !== 'object') { | ||||
| // If the JSON object does not yet have a parse method, give it one. | ||||
|  | ||||
|     if (typeof JSON.parse !== 'function') { | ||||
|         cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; | ||||
|         JSON.parse = function (text, reviver) { | ||||
|  | ||||
| // The parse method takes a text and an optional reviver function, and returns | ||||
| @@ -441,7 +464,7 @@ if (typeof JSON !== 'object') { | ||||
|             if (cx.test(text)) { | ||||
|                 text = text.replace(cx, function (a) { | ||||
|                     return '\\u' + | ||||
|                         ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|                             ('0000' + a.charCodeAt(0).toString(16)).slice(-4); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
| @@ -458,10 +481,13 @@ if (typeof JSON !== 'object') { | ||||
| // we look to see that the remaining characters are only whitespace or ']' or | ||||
| // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. | ||||
|  | ||||
|             if (/^[\],:{}\s]*$/ | ||||
|                     .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') | ||||
|             if ( | ||||
|                 /^[\],:{}\s]*$/.test( | ||||
|                     text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') | ||||
|                         .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') | ||||
|                         .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { | ||||
|                         .replace(/(?:^|:|,)(?:\s*\[)+/g, '') | ||||
|                 ) | ||||
|             ) { | ||||
|  | ||||
| // In the third stage we use the eval function to compile the text into a | ||||
| // JavaScript structure. The '{' operator is subject to a syntactic ambiguity | ||||
| @@ -474,8 +500,8 @@ if (typeof JSON !== 'object') { | ||||
| // each name/value pair to a reviver function for possible transformation. | ||||
|  | ||||
|                 return typeof reviver === 'function' | ||||
|                     ? walk({'': j}, '') | ||||
|                     : j; | ||||
|                 ? walk({'': j}, '') | ||||
|                 : j; | ||||
|             } | ||||
|  | ||||
| // If the text is not JSON parseable, then a SyntaxError is thrown. | ||||
|   | ||||
							
								
								
									
										299
									
								
								js/util.js
									
									
									
									
									
								
							
							
						
						| @@ -32,7 +32,6 @@ var SN = { // StatusNet | ||||
|             MaxLength: 140, | ||||
|             PatternUsername: /^[0-9a-zA-Z\-_.]*$/, | ||||
|             HTTP20x30x: [200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 306, 307], | ||||
|             NoticeFormMaster: null // to be cloned from the one at top | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
| @@ -58,6 +57,9 @@ var SN = { // StatusNet | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     V: {    // Variables | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Map of localized message strings exported to script from the PHP | ||||
|      * side via Action::getScriptMessages(). | ||||
| @@ -94,12 +96,12 @@ var SN = { // StatusNet | ||||
|          * @access private | ||||
|          */ | ||||
|         FormNoticeEnhancements: function (form) { | ||||
|             if (jQuery.data(form[0], 'ElementData') === undefined) { | ||||
|             if ($.data(form[0], 'ElementData') === undefined) { | ||||
|                 var MaxLength = form.find('.count').text(); | ||||
|                 if (MaxLength === undefined) { | ||||
|                     MaxLength = SN.C.I.MaxLength; | ||||
|                 } | ||||
|                 jQuery.data(form[0], 'ElementData', {MaxLength: MaxLength}); | ||||
|                 $.data(form[0], 'ElementData', {MaxLength: MaxLength}); | ||||
|  | ||||
|                 SN.U.Counter(form); | ||||
|  | ||||
| @@ -122,7 +124,7 @@ var SN = { // StatusNet | ||||
|                 NDT.on('cut', delayedUpdate) | ||||
|                     .on('paste', delayedUpdate); | ||||
|             } else { | ||||
|                 form.find('.count').text(jQuery.data(form[0], 'ElementData').MaxLength); | ||||
|                 form.find('.count').text($.data(form[0], 'ElementData').MaxLength); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
| @@ -143,7 +145,7 @@ var SN = { // StatusNet | ||||
|         Counter: function (form) { | ||||
|             SN.C.I.FormNoticeCurrent = form; | ||||
|  | ||||
|             var MaxLength = jQuery.data(form[0], 'ElementData').MaxLength; | ||||
|             var MaxLength = $.data(form[0], 'ElementData').MaxLength; | ||||
|  | ||||
|             if (MaxLength <= 0) { | ||||
|                 return; | ||||
| @@ -217,6 +219,18 @@ var SN = { // StatusNet | ||||
|             return url; | ||||
|         }, | ||||
|  | ||||
|         FormNoticeUniqueID: function (form) { | ||||
|             var oldId = form.attr('id'); | ||||
|             var newId = 'form_notice_' + Math.floor(Math.random()*999999999); | ||||
|             var attrs = ['name', 'for', 'id']; | ||||
|             for (var key in attrs) { | ||||
|                 form.find("[" + attrs[key] + "~='" + oldId + "']").each(function () { | ||||
|                         var newAttr = $(this).attr(attrs[key]).replace(oldId, newId); | ||||
|                         $(this).attr(attrs[key], newAttr); | ||||
|                     }); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Grabs form data and submits it asynchronously, with 'ajax=1' | ||||
|          * parameter added to the rest. | ||||
| @@ -375,7 +389,7 @@ var SN = { // StatusNet | ||||
|                         if ($('.' + SN.C.S.Error, response).length > 0) { | ||||
|                             form.append(document._importNode($('.' + SN.C.S.Error, response)[0], true)); | ||||
|                         } else { | ||||
|                             if (parseInt(xhr.status) === 0 || jQuery.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) { | ||||
|                             if (parseInt(xhr.status) === 0 || $.inArray(parseInt(xhr.status), SN.C.I.HTTP20x30x) >= 0) { | ||||
|                                 form | ||||
|                                     .resetForm() | ||||
|                                     .find('.attach-status').remove(); | ||||
| @@ -405,16 +419,14 @@ var SN = { // StatusNet | ||||
|                             if (replyItem.length > 0) { | ||||
|                                 // If this is an inline reply, remove the form... | ||||
|                                 var list = form.closest('.threaded-replies'); | ||||
|                                 var placeholder = list.find('.notice-reply-placeholder'); | ||||
|                                 replyItem.remove(); | ||||
|  | ||||
|                                 var id = $(notice).attr('id'); | ||||
|                                 if ($('#' + id).length == 0) { | ||||
|                                     $(notice).insertBefore(placeholder); | ||||
|                                     $(notice).insertBefore(replyItem); | ||||
|                                 } // else Realtime came through before us... | ||||
|  | ||||
|                                 // ...and show the placeholder form. | ||||
|                                 placeholder.show(); | ||||
|                                 replyItem.remove(); | ||||
|  | ||||
|                             } else if (notices.length > 0 && SN.U.belongsOnTimeline(notice)) { | ||||
|                                 // Not a reply. If on our timeline, show it at the top! | ||||
|  | ||||
| @@ -578,6 +590,44 @@ var SN = { // StatusNet | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Setup function -- DOES NOT trigger actions immediately. | ||||
|          * | ||||
|          * Sets up event handlers on all visible notice's option <a> elements | ||||
|          * with the "popup" class so they behave as expected with AJAX. | ||||
|          * | ||||
|          * (without javascript the link goes to a page that expects you to verify | ||||
|          * the action through a form) | ||||
|          * | ||||
|          * @access private | ||||
|          */ | ||||
|         NoticeOptionsAjax: function () { | ||||
|             $(document).on('click', '.notice-options > a.popup', function (e) { | ||||
|                 e.preventDefault(); | ||||
|                 var noticeEl = $(this).closest('.notice'); | ||||
|                 $.ajax({ | ||||
|                     url: $(this).attr('href'), | ||||
|                     data: {ajax: 1}, | ||||
|                     success: function (data, textStatus, xhr) { | ||||
|                         SN.U.NoticeOptionPopup(data, noticeEl); | ||||
|                     }, | ||||
|                 }); | ||||
|                 return false; | ||||
|             }); | ||||
|         }, | ||||
|  | ||||
|         NoticeOptionPopup: function (data, noticeEl) { | ||||
|             title = $('head > title', data).text(); | ||||
|             body = $('body', data).html(); | ||||
|             dialog = $(body).dialog({ | ||||
|                     height: "auto", | ||||
|                     width: "auto", | ||||
|                     modal: true, | ||||
|                     resizable: true, | ||||
|                     title: title, | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Setup function -- DOES NOT trigger actions immediately. | ||||
|          * | ||||
| @@ -616,41 +666,18 @@ var SN = { // StatusNet | ||||
|         NoticeInlineReplyTrigger: function (notice, initialText) { | ||||
|             // Find the notice we're replying to... | ||||
|             var id = $($('.notice_id', notice)[0]).text(); | ||||
|             var replyForm, placeholder; | ||||
|             var replyForm; | ||||
|             var parentNotice = notice; | ||||
|             var stripForm = true; // strip a couple things out of reply forms that are inline | ||||
|  | ||||
|             // Find the threaded replies view we'll be adding to... | ||||
|             var list = notice.closest('.notices'); | ||||
|             if (list.closest('.old-school').length) { | ||||
|                 // We're replying to an old-school conversation thread; | ||||
|                 // use the old-style ping into the top form. | ||||
|                 SN.U.switchInputFormTab("status"); | ||||
|                 replyForm = $('#input_form_status').find('form'); | ||||
|                 stripForm = false; | ||||
|             } else if (list.hasClass('threaded-replies')) { | ||||
|                 // We're replying to a reply; use reply form on the end of this list. | ||||
|                 // We'll add our form at the end of this; grab the root notice. | ||||
|                 parentNotice = list.closest('.notice'); | ||||
|  | ||||
|                 // See if the form's already open... | ||||
|                 replyForm = $('.notice-reply-form', list); | ||||
|             } else { | ||||
|                 // We're replying to a parent notice; pull its threaded list | ||||
|                 // and we'll add on the end of it. Will add if needed. | ||||
|                 list = $('ul.threaded-replies', notice); | ||||
|                 if (list.length == 0) { | ||||
|                     SN.U.NoticeInlineReplyPlaceholder(notice); | ||||
|                     list = $('ul.threaded-replies', notice); | ||||
|                 } else { | ||||
|                     placeholder = $('li.notice-reply-placeholder', notice); | ||||
|                     if (placeholder.length == 0) { | ||||
|                         SN.U.NoticeInlineReplyPlaceholder(notice); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // See if the form's already open... | ||||
|                 replyForm = $('.notice-reply-form', list); | ||||
|             var list = notice.find('.threaded-replies'); | ||||
|             if (list.length == 0) { | ||||
|                 list = notice.closest('.threaded-replies'); | ||||
|             } | ||||
|             if (list.length == 0) { | ||||
|                 list = $('<ul class="notices threaded-replies xoxo"></ul>'); | ||||
|                 notice.append(list); | ||||
|                 list = notice.find('.threaded-replies'); | ||||
|             } | ||||
|  | ||||
|             var nextStep = function () { | ||||
| @@ -663,6 +690,7 @@ var SN = { // StatusNet | ||||
|                     replyForm.find('label[for=notice_to]').hide(); | ||||
|                     replyForm.find('label[for=notice_private]').hide(); | ||||
|                 } | ||||
|                 replyItem.show(); | ||||
|  | ||||
|                 // Set focus... | ||||
|                 var text = replyForm.find('textarea'); | ||||
| @@ -681,82 +709,64 @@ var SN = { // StatusNet | ||||
|                     text[0].setSelectionRange(len, len); | ||||
|                 } | ||||
|             }; | ||||
|             if (replyForm.length > 0) { | ||||
|                 // Update the existing form... | ||||
|                 nextStep(); | ||||
|             } else { | ||||
|                 // Hide the placeholder... | ||||
|                 placeholder = list.find('li.notice-reply-placeholder').hide(); | ||||
|  | ||||
|                 // Create the reply form entry at the end | ||||
|                 var replyItem = $('li.notice-reply', list); | ||||
|                 if (replyItem.length == 0) { | ||||
|                     replyItem = $('<li class="notice-reply"></li>'); | ||||
|             // Create the reply form entry | ||||
|             var replyItem = $('li.notice-reply', list); | ||||
|             if (replyItem.length == 0) { | ||||
|                 replyItem = $('<li class="notice-reply"></li>'); | ||||
|             } | ||||
|             replyForm = replyItem.children('form'); | ||||
|             if (replyForm.length == 0) { | ||||
|                 // Let's try another trick to avoid fetching by URL | ||||
|                 var noticeForm = $('#input_form_status > form'); | ||||
|                 if (noticeForm.length == 0) { | ||||
|                     // No notice form found on the page, so let's just | ||||
|                     // fetch a fresh copy of the notice form over AJAX. | ||||
|                     $.ajax({ | ||||
|                         url: SN.V.urlNewNotice, | ||||
|                         data: {ajax: 1, inreplyto: id}, | ||||
|                         success: function (data, textStatus, xhr) { | ||||
|                             var formEl = document._importNode($('form', data)[0], true); | ||||
|                             replyForm = $(formEl); | ||||
|                             replyItem.append(replyForm); | ||||
|                             list.append(replyItem); | ||||
|  | ||||
|                     var intermediateStep = function (formMaster) { | ||||
|                         var formEl = document._importNode(formMaster, true); | ||||
|                         replyItem.append(formEl); | ||||
|                         list.append(replyItem); // *after* the placeholder | ||||
|  | ||||
|                         var form = $(formEl); | ||||
|                         replyForm = form; | ||||
|                         SN.Init.NoticeFormSetup(form); | ||||
|  | ||||
|                         nextStep(); | ||||
|                     }; | ||||
|                     if (SN.C.I.NoticeFormMaster) { | ||||
|                         // We've already saved a master copy of the form. | ||||
|                         // Clone it in! | ||||
|                         intermediateStep(SN.C.I.NoticeFormMaster); | ||||
|                     } else { | ||||
|                         // Fetch a fresh copy of the notice form over AJAX. | ||||
|                         // Warning: this can have a delay, which looks bad. | ||||
|                         // @fixme this fallback may or may not work | ||||
|                         var url = $('#form_notice').attr('action'); | ||||
|                         $.get(url, {ajax: 1}, function (data, textStatus, xhr) { | ||||
|                             intermediateStep($('form', data)[0]); | ||||
|                         }); | ||||
|                     } | ||||
|                             SN.Init.NoticeFormSetup(replyForm); | ||||
|                             nextStep(); | ||||
|                         }, | ||||
|                     }); | ||||
|                     // We do everything relevant in 'success' above | ||||
|                     return; | ||||
|                 } | ||||
|                 replyForm = noticeForm.clone(); | ||||
|                 SN.Init.NoticeFormSetup(replyForm); | ||||
|                 replyItem.append(replyForm); | ||||
|                 list.append(replyItem); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         NoticeInlineReplyPlaceholder: function (notice) { | ||||
|             var list = notice.find('ul.threaded-replies'); | ||||
|             if (list.length == 0) { | ||||
|                 list = $('<ul class="notices threaded-replies xoxo"></ul>'); | ||||
|                 notice.append(list); | ||||
|                 list = notice.find('ul.threaded-replies'); | ||||
|             } | ||||
|             var placeholder = $('<li class="notice-reply-placeholder">' + | ||||
|                                     '<input class="placeholder" />' + | ||||
|                                 '</li>'); | ||||
|             placeholder.find('input') | ||||
|                 .val(SN.msg('reply_placeholder')); | ||||
|             list.append(placeholder); | ||||
|             // replyForm is set, we're not fetching by URL... | ||||
|             // Next setp is to configure in-reply-to etc. | ||||
|             nextStep(); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Setup function -- DOES NOT apply immediately. | ||||
|          * | ||||
|          * Sets up event handlers for inline reply mini-form placeholders. | ||||
|          * Uses 'on' rather than 'live' or 'bind', so applies to future as well as present items. | ||||
|          */ | ||||
|         NoticeInlineReplySetup: function () { | ||||
|             $('li.notice-reply-placeholder input') | ||||
|                 .on('focus', function () { | ||||
|                     var notice = $(this).closest('li.notice'); | ||||
|                     SN.U.NoticeInlineReplyTrigger(notice); | ||||
|                     return false; | ||||
|                 }); | ||||
|             // Expand conversation links | ||||
|             $(document).on('click', 'li.notice-reply-comments a', function () { | ||||
|                     var url = $(this).attr('href'); | ||||
|                     var area = $(this).closest('.threaded-replies'); | ||||
|                     $.get(url, {ajax: 1}, function (data, textStatus, xhr) { | ||||
|                         var replies = $('.threaded-replies', data); | ||||
|                         if (replies.length) { | ||||
|                             area.replaceWith(document._importNode(replies[0], true)); | ||||
|                         } | ||||
|                     $.ajax({ | ||||
|                         url: url, | ||||
|                         data: {ajax: 1}, | ||||
|                         success: function (data, textStatus, xhr) { | ||||
|                             var replies = $('.threaded-replies', data); | ||||
|                             if (replies.length) { | ||||
|                                 area.replaceWith(document._importNode(replies[0], true)); | ||||
|                             } | ||||
|                         }, | ||||
|                     }); | ||||
|                     return false; | ||||
|                 }); | ||||
| @@ -1043,7 +1053,7 @@ var SN = { // StatusNet | ||||
|  | ||||
|             function removeNoticeDataGeo(error) { | ||||
|                 label | ||||
|                     .attr('title', jQuery.trim(label.text())) | ||||
|                     .attr('title', $.trim(label.text())) | ||||
|                     .removeClass('checked'); | ||||
|  | ||||
|                 form.find('[name=lat]').val(''); | ||||
| @@ -1460,16 +1470,13 @@ var SN = { // StatusNet | ||||
|                                 // Only close if there's been no edit. | ||||
|                                 if (cur == '' || cur == textarea.data('initialText')) { | ||||
|                                     var parentNotice = replyItem.closest('li.notice'); | ||||
|                                     replyItem.remove(); | ||||
|                                     replyItem.hide(); | ||||
|                                     parentNotice.find('li.notice-reply-placeholder').show(); | ||||
|                                 } | ||||
|                             } | ||||
|                         }); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 // Infield labels for notice form inputs. | ||||
|                 $('.input_forms fieldset fieldset label').inFieldLabels({ fadeOpacity:0 }); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
| @@ -1481,13 +1488,15 @@ var SN = { // StatusNet | ||||
|          * @param {jQuery} form | ||||
|          */ | ||||
|         NoticeFormSetup: function (form) { | ||||
|             if (!form.data('NoticeFormSetup')) { | ||||
|                 SN.U.NoticeLocationAttach(form); | ||||
|                 SN.U.FormNoticeXHR(form); | ||||
|                 SN.U.FormNoticeEnhancements(form); | ||||
|                 SN.U.NoticeDataAttach(form); | ||||
|                 form.data('NoticeFormSetup', true); | ||||
|             if (form.data('NoticeFormSetup')) { | ||||
|                 return false; | ||||
|             } | ||||
|             SN.U.NoticeLocationAttach(form); | ||||
|             SN.U.FormNoticeUniqueID(form); | ||||
|             SN.U.FormNoticeXHR(form); | ||||
|             SN.U.FormNoticeEnhancements(form); | ||||
|             SN.U.NoticeDataAttach(form); | ||||
|             form.data('NoticeFormSetup', true); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
| @@ -1498,13 +1507,10 @@ var SN = { // StatusNet | ||||
|          */ | ||||
|         Notices: function () { | ||||
|             if ($('body.user_in').length > 0) { | ||||
|                 var masterForm = $('.form_notice:first'); | ||||
|                 if (masterForm.length > 0) { | ||||
|                     SN.C.I.NoticeFormMaster = document._importNode(masterForm[0], true); | ||||
|                 } | ||||
|                 SN.U.NoticeRepeat(); | ||||
|                 SN.U.NoticeReply(); | ||||
|                 SN.U.NoticeInlineReplySetup(); | ||||
|                 SN.U.NoticeOptionsAjax(); | ||||
|             } | ||||
|  | ||||
|             SN.U.NoticeAttachments(); | ||||
| @@ -1562,60 +1568,6 @@ var SN = { // StatusNet | ||||
|             }); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Called when a people tag edit box is shown in the interface | ||||
|          * | ||||
|          * - loads the jQuery UI autocomplete plugin | ||||
|          * - sets event handlers for tag completion | ||||
|          * | ||||
|          */ | ||||
|         PeopletagAutocomplete: function (txtBox) { | ||||
|             var split = function (val) { | ||||
|                 return val.split( /\s+/ ); | ||||
|             } | ||||
|             var extractLast = function (term) { | ||||
|                 return split(term).pop(); | ||||
|             } | ||||
|  | ||||
|             // don't navigate away from the field on tab when selecting an item | ||||
|             txtBox.on( "keydown", function ( event ) { | ||||
|                 if ( event.keyCode === $.ui.keyCode.TAB && | ||||
|                         $(this).data( "autocomplete" ).menu.active ) { | ||||
|                     event.preventDefault(); | ||||
|                 } | ||||
|             }).autocomplete({ | ||||
|                 minLength: 0, | ||||
|                 source: function (request, response) { | ||||
|                     // delegate back to autocomplete, but extract the last term | ||||
|                     response($.ui.autocomplete.filter( | ||||
|                         SN.C.PtagACData, extractLast(request.term))); | ||||
|                 }, | ||||
|                 focus: function () { | ||||
|                     return false; | ||||
|                 }, | ||||
|                 select: function (event, ui) { | ||||
|                     var terms = split(this.value); | ||||
|                     terms.pop(); | ||||
|                     terms.push(ui.item.value); | ||||
|                     terms.push(""); | ||||
|                     this.value = terms.join(" "); | ||||
|                     return false; | ||||
|                 } | ||||
|             }).data('autocomplete')._renderItem = function (ul, item) { | ||||
|                     // FIXME: with jQuery UI you cannot have it highlight the match | ||||
|                     var _l = '<a class="ptag-ac-line-tag">' + item.tag | ||||
|                           + ' <em class="privacy_mode">' + item.mode + '</em>' | ||||
|                           + '<span class="freq">' + item.freq + '</span></a>' | ||||
|  | ||||
|                     return $("<li/>") | ||||
|                             .addClass('mode-' + item.mode) | ||||
|                             .addClass('ptag-ac-line') | ||||
|                             .data("item.autocomplete", item) | ||||
|                             .append(_l) | ||||
|                             .appendTo(ul); | ||||
|                 } | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * Run setup for the ajax people tags editor | ||||
|          * | ||||
| @@ -1644,7 +1596,6 @@ var SN = { // StatusNet | ||||
|                         } | ||||
|  | ||||
|                         SN.C.PtagACData = data; | ||||
|                         SN.Init.PeopletagAutocomplete(form.find('#tags')); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|   | ||||
| @@ -118,16 +118,18 @@ class Action extends HTMLOutputter // lawsuit | ||||
|             common_config_set('db', 'database', $mirror); | ||||
|         } | ||||
|  | ||||
|         $status = $this->prepare($args); | ||||
|         if ($status) { | ||||
|             $this->handle($args); | ||||
|         } else { | ||||
|             common_debug('Prepare failed for Action.'); | ||||
|         if (Event::handle('StartActionExecute', array($this, &$args))) { | ||||
|             $prepared = $this->prepare($args); | ||||
|             if ($prepared) { | ||||
|                 $this->handle($args); | ||||
|             } else { | ||||
|                 common_debug('Prepare failed for Action.'); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->flush(); | ||||
|  | ||||
|         Event::handle('EndActionExecute', array($status, $this)); | ||||
|         Event::handle('EndActionExecute', array($this)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -156,8 +158,8 @@ class Action extends HTMLOutputter // lawsuit | ||||
|         $this->action = strtolower($this->trimmed('action')); | ||||
|  | ||||
|         if ($this->ajax || $this->boolean('ajax')) { | ||||
|             // check with StatusNet::isAjax() | ||||
|             StatusNet::setAjax(true); | ||||
|             // check with GNUsocial::isAjax() | ||||
|             GNUsocial::setAjax(true); | ||||
|         } | ||||
|  | ||||
|         if ($this->needLogin) { | ||||
| @@ -206,7 +208,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|      */ | ||||
|     function showPage() | ||||
|     { | ||||
|         if (StatusNet::isAjax()) { | ||||
|         if (GNUsocial::isAjax()) { | ||||
|             self::showAjax(); | ||||
|             return; | ||||
|         } | ||||
| @@ -326,7 +328,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|         } else { | ||||
|             // favicon.ico should be HTTPS if the rest of the page is | ||||
|             $this->element('link', array('rel' => 'shortcut icon', | ||||
|                                          'href' => common_path('favicon.ico', StatusNet::isHTTPS()))); | ||||
|                                          'href' => common_path('favicon.ico', GNUsocial::isHTTPS()))); | ||||
|         } | ||||
|  | ||||
|         if (common_config('site', 'mobile')) { | ||||
| @@ -415,8 +417,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|                 $this->script('extlib/jquery.form.js'); | ||||
|                 $this->script('extlib/jquery-ui/jquery-ui.js'); | ||||
|                 $this->script('extlib/jquery.cookie.js'); | ||||
|                 $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.js', StatusNet::isHTTPS()).'"); }'); | ||||
|                 $this->script('extlib/jquery.infieldlabel.js'); | ||||
|                 $this->inlineScript('if (typeof window.JSON !== "object") { $.getScript("'.common_path('js/extlib/json2.js', GNUsocial::isHTTPS()).'"); }'); | ||||
|  | ||||
|                 Event::handle('EndShowJQueryScripts', array($this)); | ||||
|             } | ||||
| @@ -430,6 +431,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|                 $this->inlineScript('var _peopletagAC = "' . | ||||
|                                     common_local_url('peopletagautocomplete') . '";'); | ||||
|                 $this->showScriptMessages(); | ||||
|                 $this->showScriptVariables(); | ||||
|                 // Anti-framing code to avoid clickjacking attacks in older browsers. | ||||
|                 // This will show a blank page if the page is being framed, which is | ||||
|                 // consistent with the behavior of the 'X-Frame-Options: SAMEORIGIN' | ||||
| @@ -460,12 +462,6 @@ class Action extends HTMLOutputter // lawsuit | ||||
|             // TRANS: Localized tooltip for '...' expansion button on overlong remote messages. | ||||
|             $messages['showmore_tooltip'] = _m('TOOLTIP', 'Show more'); | ||||
|  | ||||
|             // TRANS: Inline reply form submit button: submits a reply comment. | ||||
|             $messages['reply_submit'] = _m('BUTTON', 'Reply'); | ||||
|  | ||||
|             // TRANS: Placeholder text for inline reply form. Clicking in this box will turn it into a mini notice form. | ||||
|             $messages['reply_placeholder'] = _m('Write a reply...'); | ||||
|  | ||||
|             $messages = array_merge($messages, $this->getScriptMessages()); | ||||
|  | ||||
|             Event::handle('EndScriptMessages', array($this, &$messages)); | ||||
| @@ -478,6 +474,19 @@ class Action extends HTMLOutputter // lawsuit | ||||
|         return $messages; | ||||
|     } | ||||
|  | ||||
|     protected function showScriptVariables() | ||||
|     { | ||||
|         $vars = array(); | ||||
|  | ||||
|         if (Event::handle('StartScriptVariables', array($this, &$vars))) { | ||||
|             $vars['urlNewNotice'] = common_local_url('newnotice'); | ||||
|         } | ||||
|         if (!empty($vars)) { | ||||
|             $this->inlineScript('SN.V = ' . json_encode($vars)); | ||||
|         } | ||||
|         return $vars; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * If the action will need localizable text strings, export them here like so: | ||||
|      * | ||||
| @@ -638,7 +647,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|             $this->elementStart('a', array('class' => 'home bookmark', | ||||
|                                            'href' => $url)); | ||||
|  | ||||
|             if (StatusNet::isHTTPS()) { | ||||
|             if (GNUsocial::isHTTPS()) { | ||||
|                 $logoUrl = common_config('site', 'ssllogo'); | ||||
|                 if (empty($logoUrl)) { | ||||
|                     // if logo is an uploaded file, try to fall back to HTTPS file URL | ||||
| @@ -1144,7 +1153,7 @@ class Action extends HTMLOutputter // lawsuit | ||||
|                 $image    = common_config('license', 'image'); | ||||
|                 $sslimage = common_config('license', 'sslimage'); | ||||
|  | ||||
|                 if (StatusNet::isHTTPS()) { | ||||
|                 if (GNUsocial::isHTTPS()) { | ||||
|                     if (!empty($sslimage)) { | ||||
|                         $url = $sslimage; | ||||
|                     } else if (preg_match('#^http://i.creativecommons.org/#', $image)) { | ||||
| @@ -1354,6 +1363,19 @@ class Action extends HTMLOutputter // lawsuit | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * This is a cheap hack to avoid a bug in DB_DataObject | ||||
|      * where '' is non-type-aware compared to 0, which means it | ||||
|      * will always be true for values like false and 0 too... | ||||
|      * | ||||
|      * Upstream bug is:: | ||||
|      * https://pear.php.net/bugs/bug.php?id=20291 | ||||
|      */ | ||||
|     function booleanintstring($key, $def=false) | ||||
|     { | ||||
|         return $this->boolean($key, $def) ? '1' : '0'; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Integer value of an argument | ||||
|      * | ||||
|   | ||||
| @@ -160,10 +160,11 @@ abstract class ActivityHandlerPlugin extends Plugin | ||||
|     * @fixme are there any standard options? | ||||
|     * | ||||
|     * @param Activity $activity | ||||
|     * @param Profile $actor | ||||
|     * @param Notice   $stored       The notice in our database for this certain object | ||||
|     * @param array $options=array() | ||||
|     * | ||||
|     * @return Notice the resulting notice | ||||
|     * @return object    If the verb handling plugin creates an object, it can be returned here (otherwise true) | ||||
|     * @throws exception On any error. | ||||
|     */ | ||||
|     protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array()) | ||||
|     { | ||||
| @@ -174,7 +175,7 @@ abstract class ActivityHandlerPlugin extends Plugin | ||||
|      * This usually gets called from Notice::saveActivity after a Notice object has been created, | ||||
|      * so it contains a proper id and a uri for the object to be saved. | ||||
|      */ | ||||
|     public function onStoreActivityObject(Activity $act, Notice $stored, array $options=array(), &$object) { | ||||
|     public function onStoreActivityObject(Activity $act, Notice $stored, array $options, &$object) { | ||||
|         // $this->oldSaveNew is there during a migration period of plugins, to start using | ||||
|         // Notice::saveActivity instead of Notice::saveNew | ||||
|         if (!$this->isMyActivity($act) || isset($this->oldSaveNew)) { | ||||
| @@ -182,7 +183,13 @@ abstract class ActivityHandlerPlugin extends Plugin | ||||
|         } | ||||
|         $object = $this->saveObjectFromActivity($act, $stored, $options); | ||||
|         try { | ||||
|             $act->context->attention = array_merge($act->context->attention, $object->getAttentionArray()); | ||||
|             // In the future we probably want to use something like ActivityVerb_DataObject for the kind | ||||
|             // of objects which are returned from saveObjectFromActivity. | ||||
|             if ($object instanceof Managed_DataObject) { | ||||
|                 // If the verb handling plugin figured out some more attention URIs, add them here to the | ||||
|                 // original activity. This is only done if a separate object is actually needed to be saved. | ||||
|                 $act->context->attention = array_merge($act->context->attention, $object->getAttentionArray()); | ||||
|             } | ||||
|         } catch (Exception $e) { | ||||
|             common_debug('WARNING: Could not get attention list from object '.get_class($object).'!'); | ||||
|         } | ||||
| @@ -595,7 +602,6 @@ abstract class ActivityHandlerPlugin extends Plugin | ||||
|         $nli->showNoticeSource(); | ||||
|         $nli->showNoticeLocation(); | ||||
|         $nli->showPermalink(); | ||||
|         $nli->showRepeat(); | ||||
|  | ||||
|         $nli->showNoticeOptions(); | ||||
|     } | ||||
|   | ||||
| @@ -109,7 +109,7 @@ class ActivityImporter extends QueueHandler | ||||
|  | ||||
|             // XXX: don't do this for untrusted input! | ||||
|  | ||||
|             Subscription::start($otherProfile, $profile); | ||||
|             Subscription::ensureStart($otherProfile, $profile); | ||||
|         } else if (empty($activity->actor) | ||||
|                    || $activity->actor->id == $author->id) { | ||||
|  | ||||
| @@ -123,7 +123,7 @@ class ActivityImporter extends QueueHandler | ||||
|                 throw new ClientException(_('Unknown profile.')); | ||||
|             } | ||||
|  | ||||
|             Subscription::start($profile, $otherProfile); | ||||
|             Subscription::ensureStart($profile, $otherProfile); | ||||
|         } else { | ||||
|             // TRANS: Client exception thrown when trying to import an event not related to the importing user. | ||||
|             throw new Exception(_('This activity seems unrelated to our user.')); | ||||
| @@ -213,7 +213,7 @@ class ActivityImporter extends QueueHandler | ||||
|  | ||||
|         // Get (safe!) HTML and text versions of the content | ||||
|  | ||||
|         $rendered = $this->purify($sourceContent); | ||||
|         $rendered = common_purify($sourceContent); | ||||
|         $content = common_strip_html($rendered); | ||||
|  | ||||
|         $shortened = $user->shortenLinks($content); | ||||
| @@ -338,15 +338,4 @@ class ActivityImporter extends QueueHandler | ||||
|  | ||||
|         return array($groups, $replies); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function purify($content) | ||||
|     { | ||||
|         require_once INSTALLDIR.'/extlib/htmLawed/htmLawed.php'; | ||||
|  | ||||
|         $config = array('safe' => 1, | ||||
|                         'deny_attribute' => 'id,style,on*'); | ||||
|  | ||||
|         return htmLawed($content, $config); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -146,7 +146,7 @@ class ActivityMover extends QueueHandler | ||||
|                                "Changing sub to {$act->objects[0]->id}". | ||||
|                                "by {$act->actor->id} to {$remote->nickname}."); | ||||
|                     $otherProfile = $otherUser->getProfile(); | ||||
|                     Subscription::start($otherProfile, $remote); | ||||
|                     Subscription::ensureStart($otherProfile, $remote); | ||||
|                     Subscription::cancel($otherProfile, $user->getProfile()); | ||||
|                 } else { | ||||
|                     $this->log(LOG_NOTICE, | ||||
|   | ||||
| @@ -512,11 +512,11 @@ class ActivityObject | ||||
|  | ||||
|             switch (self::canonicalType($object->type)) { | ||||
|             case 'image': | ||||
|                 $object->largerImage = $file->url; | ||||
|                 $object->largerImage = $file->getUrl(); | ||||
|                 break; | ||||
|             case 'video': | ||||
|             case 'audio': | ||||
|                 $object->stream = $file->url; | ||||
|                 $object->stream = $file->getUrl(); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
| @@ -861,7 +861,7 @@ class ActivityObject | ||||
|                 if (is_string($this->thumbnail)) { | ||||
|                     $object['image'] = array('url' => $this->thumbnail); | ||||
|                 } else { | ||||
|                     $object['image'] = array('url' => $this->thumbnail->url); | ||||
|                     $object['image'] = array('url' => $this->thumbnail->getUrl()); | ||||
|                     if ($this->thumbnail->width) { | ||||
|                         $object['image']['width'] = $this->thumbnail->width; | ||||
|                     } | ||||
|   | ||||
| @@ -48,6 +48,7 @@ class ActivityVerb | ||||
|     const SHARE    = 'http://activitystrea.ms/schema/1.0/share'; | ||||
|     const SAVE     = 'http://activitystrea.ms/schema/1.0/save'; | ||||
|     const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite'; | ||||
|     const LIKE     = 'http://activitystrea.ms/schema/1.0/like'; // This is a synonym of favorite | ||||
|     const PLAY     = 'http://activitystrea.ms/schema/1.0/play'; | ||||
|     const FOLLOW   = 'http://activitystrea.ms/schema/1.0/follow'; | ||||
|     const FRIEND   = 'http://activitystrea.ms/schema/1.0/make-friend'; | ||||
| @@ -56,7 +57,8 @@ class ActivityVerb | ||||
|  | ||||
|     // Custom OStatus verbs for the flipside until they're standardized | ||||
|     const DELETE     = 'http://ostatus.org/schema/1.0/unfollow'; | ||||
|     const UNFAVORITE = 'http://ostatus.org/schema/1.0/unfavorite'; | ||||
|     const UNFAVORITE = 'http://activitystrea.ms/schema/1.0/unfavorite'; | ||||
|     const UNLIKE     = 'http://activitystrea.ms/schema/1.0/unlike'; // This is a synonym of unfavorite | ||||
|     const UNFOLLOW   = 'http://ostatus.org/schema/1.0/unfollow'; | ||||
|     const LEAVE      = 'http://ostatus.org/schema/1.0/leave'; | ||||
|     const UNTAG      = 'http://ostatus.org/schema/1.0/untag'; | ||||
|   | ||||
| @@ -59,24 +59,8 @@ class AdminPanelNav extends Menu | ||||
|         $nickname = $user->nickname; | ||||
|         $name = $user->getProfile()->getBestName(); | ||||
|  | ||||
|         // Stub section w/ home link | ||||
|         $this->action->elementStart('ul'); | ||||
|         $this->action->elementStart('li'); | ||||
|         // TRANS: Header in administrator navigation panel. | ||||
|         $this->action->element('h3', null, _m('HEADER','Home')); | ||||
|         $this->action->elementStart('ul', 'nav'); | ||||
|         $this->out->menuItem(common_local_url('all', array('nickname' => | ||||
|                                                            $nickname)), | ||||
|                              // TRANS: Menu item in administrator navigation panel. | ||||
|                              _m('MENU','Home'), | ||||
|                              // TRANS: Menu item title in administrator navigation panel. | ||||
|                              // TRANS: %s is a username. | ||||
|                              sprintf(_('%s and friends'), $name), | ||||
|                              $this->action == 'all', 'nav_timeline_personal'); | ||||
|  | ||||
|         $this->action->elementEnd('ul'); | ||||
|         $this->action->elementEnd('li'); | ||||
|         $this->action->elementEnd('ul'); | ||||
|         $stub = new HomeStubNav($this->action); | ||||
|         $this->submenu(_m('MENU','Home'), $stub); | ||||
|  | ||||
|         $this->action->elementStart('ul'); | ||||
|         $this->action->elementStart('li'); | ||||
|   | ||||
| @@ -144,7 +144,7 @@ class ApiAction extends Action | ||||
|      */ | ||||
|     protected function prepare(array $args=array()) | ||||
|     { | ||||
|         StatusNet::setApi(true); // reduce exception reports to aid in debugging | ||||
|         GNUsocial::setApi(true); // reduce exception reports to aid in debugging | ||||
|         parent::prepare($args); | ||||
|  | ||||
|         $this->format   = $this->arg('format'); | ||||
| @@ -159,7 +159,7 @@ class ApiAction extends Action | ||||
|         $this->limit    = $this->count + 1; | ||||
|  | ||||
|         if ($this->arg('since')) { | ||||
|             header('X-StatusNet-Warning: since parameter is disabled; use since_id'); | ||||
|             header('X-GNUsocial-Warning: since parameter is disabled; use since_id'); | ||||
|         } | ||||
|  | ||||
|         $this->source = $this->trimmed('source'); | ||||
| @@ -264,22 +264,20 @@ class ApiAction extends Action | ||||
|         $twitter_user['statuses_count'] = $profile->noticeCount(); | ||||
|  | ||||
|         // Is the requesting user following this user? | ||||
|         // These values might actually also mean "unknown". Ambiguity issues? | ||||
|         $twitter_user['following'] = false; | ||||
|         $twitter_user['statusnet_blocking'] = false; | ||||
|         $twitter_user['notifications'] = false; | ||||
|  | ||||
|         if (isset($this->auth_user)) { | ||||
|  | ||||
|             $twitter_user['following'] = $this->auth_user->isSubscribed($profile); | ||||
|             $twitter_user['statusnet_blocking']  = $this->auth_user->hasBlocked($profile); | ||||
|  | ||||
|             // Notifications on? | ||||
|             $sub = Subscription::pkeyGet(array('subscriber' => | ||||
|                                                $this->auth_user->id, | ||||
|                                                'subscribed' => $profile->id)); | ||||
|  | ||||
|             if ($sub) { | ||||
|         if ($this->scoped instanceof Profile) { | ||||
|             try { | ||||
|                 $sub = Subscription::getSubscription($this->scoped, $profile); | ||||
|                 // Notifications on? | ||||
|                 $twitter_user['following'] = true; | ||||
|                 $twitter_user['statusnet_blocking']  = $this->scoped->hasBlocked($profile); | ||||
|                 $twitter_user['notifications'] = ($sub->jabber || $sub->sms); | ||||
|             } catch (NoResultException $e) { | ||||
|                 // well, the values are already false... | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -305,6 +303,7 @@ class ApiAction extends Action | ||||
|     { | ||||
|         $base = $this->twitterSimpleStatusArray($notice, $include_user); | ||||
|  | ||||
|         // FIXME: MOVE TO SHARE PLUGIN | ||||
|         if (!empty($notice->repeat_of)) { | ||||
|             $original = Notice::getKV('id', $notice->repeat_of); | ||||
|             if ($original instanceof Notice) { | ||||
| @@ -376,12 +375,6 @@ class ApiAction extends Action | ||||
|             $twitter_status['geo'] = null; | ||||
|         } | ||||
|  | ||||
|         if (!is_null($this->scoped)) { | ||||
|             $twitter_status['repeated']  = $this->scoped->hasRepeated($notice); | ||||
|         } else { | ||||
|             $twitter_status['repeated'] = false; | ||||
|         } | ||||
|  | ||||
|         // Enclosures | ||||
|         $attachments = $notice->attachments(); | ||||
|  | ||||
| @@ -430,11 +423,11 @@ class ApiAction extends Action | ||||
|         $twitter_group['nickname'] = $group->nickname; | ||||
|         $twitter_group['fullname'] = $group->fullname; | ||||
|  | ||||
|         if (isset($this->auth_user)) { | ||||
|             $twitter_group['member'] = $this->auth_user->isMember($group); | ||||
|         if ($this->scoped instanceof Profile) { | ||||
|             $twitter_group['member'] = $this->scoped->isMember($group); | ||||
|             $twitter_group['blocked'] = Group_block::isBlocked( | ||||
|                 $group, | ||||
|                 $this->auth_user->getProfile() | ||||
|                 $this->scoped | ||||
|             ); | ||||
|         } | ||||
|  | ||||
| @@ -485,8 +478,8 @@ class ApiAction extends Action | ||||
|         $twitter_list['member_count'] = $list->taggedCount(); | ||||
|         $twitter_list['uri'] = $list->getUri(); | ||||
|  | ||||
|         if (isset($this->auth_user)) { | ||||
|             $twitter_list['following'] = $list->hasSubscriber($this->auth_user); | ||||
|         if ($this->scoped instanceof Profile) { | ||||
|             $twitter_list['following'] = $list->hasSubscriber($this->scoped); | ||||
|         } else { | ||||
|             $twitter_list['following'] = false; | ||||
|         } | ||||
| @@ -575,37 +568,30 @@ class ApiAction extends Action | ||||
|         $relationship = array(); | ||||
|  | ||||
|         $relationship['source'] = | ||||
|             $this->relationshipDetailsArray($source, $target); | ||||
|             $this->relationshipDetailsArray($source->getProfile(), $target->getProfile()); | ||||
|         $relationship['target'] = | ||||
|             $this->relationshipDetailsArray($target, $source); | ||||
|             $this->relationshipDetailsArray($target->getProfile(), $source->getProfile()); | ||||
|  | ||||
|         return array('relationship' => $relationship); | ||||
|     } | ||||
|  | ||||
|     function relationshipDetailsArray($source, $target) | ||||
|     function relationshipDetailsArray(Profile $source, Profile $target) | ||||
|     { | ||||
|         $details = array(); | ||||
|  | ||||
| 		$source_profile = $source->getProfile(); | ||||
| 		$target_profile = $target->getProfile();		 | ||||
|         $details['screen_name'] = $source->getNickname(); | ||||
|         $details['followed_by'] = $target->isSubscribed($source); | ||||
|  | ||||
|         $details['screen_name'] = $source->nickname; | ||||
|         $details['followed_by'] = $target->isSubscribed($source_profile); | ||||
|         $details['following'] = $source->isSubscribed($target_profile); | ||||
|  | ||||
|         $notifications = false; | ||||
|  | ||||
|         if ($source->isSubscribed($target_profile)) { | ||||
|             $sub = Subscription::pkeyGet(array('subscriber' => | ||||
|                 $source->id, 'subscribed' => $target->id)); | ||||
|  | ||||
|             if (!empty($sub)) { | ||||
|                 $notifications = ($sub->jabber || $sub->sms); | ||||
|             } | ||||
|         try { | ||||
|             $sub = Subscription::getSubscription($source, $target); | ||||
|             $details['following'] = true; | ||||
|             $details['notifications_enabled'] = ($sub->jabber || $sub->sms); | ||||
|         } catch (NoResultException $e) { | ||||
|             $details['following'] = false; | ||||
|             $details['notifications_enabled'] = false; | ||||
|         } | ||||
|  | ||||
|         $details['notifications_enabled'] = $notifications; | ||||
|         $details['blocking'] = $source->hasBlocked($target_profile); | ||||
|         $details['blocking'] = $source->hasBlocked($target); | ||||
|         $details['id'] = intval($source->id); | ||||
|  | ||||
|         return $details; | ||||
| @@ -655,6 +641,7 @@ class ApiAction extends Action | ||||
|                 $this->showGeoXML($value); | ||||
|                 break; | ||||
|             case 'retweeted_status': | ||||
|                 // FIXME: MOVE TO SHARE PLUGIN | ||||
|                 $this->showTwitterXmlStatus($value, 'retweeted_status'); | ||||
|                 break; | ||||
|             default: | ||||
| @@ -788,7 +775,7 @@ class ApiAction extends Action | ||||
|     function showSingleAtomStatus($notice) | ||||
|     { | ||||
|         header('Content-Type: application/atom+xml; charset=utf-8'); | ||||
|         print $notice->asAtomEntry(true, true, true, $this->auth_user->getProfile()); | ||||
|         print $notice->asAtomEntry(true, true, true, $this->scoped); | ||||
|     } | ||||
|  | ||||
|     function show_single_json_status($notice) | ||||
| @@ -1352,7 +1339,7 @@ class ApiAction extends Action | ||||
|                 return User::getKV('nickname', $nickname); | ||||
|             } else { | ||||
|                 // Fall back to trying the currently authenticated user | ||||
|                 return $this->auth_user; | ||||
|                 return $this->scoped->getUser(); | ||||
|             } | ||||
|  | ||||
|         } else if (self::is_decimal($id)) { | ||||
| @@ -1448,7 +1435,7 @@ class ApiAction extends Action | ||||
|             } | ||||
|  | ||||
|             if (!empty($list) && $list->private) { | ||||
|                 if ($this->auth_user->id == $list->tagger) { | ||||
|                 if ($this->scoped->id == $list->tagger) { | ||||
|                     return $list; | ||||
|                 } | ||||
|             } else { | ||||
| @@ -1516,6 +1503,11 @@ class ApiAction extends Action | ||||
|             $aargs['id'] = $id; | ||||
|         } | ||||
|  | ||||
|         $user = $this->arg('user'); | ||||
|         if (!empty($user)) { | ||||
|             $aargs['user'] = $user; | ||||
|         } | ||||
|  | ||||
|         $tag = $this->arg('tag'); | ||||
|         if (!empty($tag)) { | ||||
|             $aargs['tag'] = $tag; | ||||
|   | ||||