From e269a3fad91ee1df366774eb86e921b0c2498a7e Mon Sep 17 00:00:00 2001 From: abjectio Date: Sat, 6 Jun 2015 11:28:34 +0200 Subject: [PATCH 001/156] Added ChooseTheme plugin to the repository --- avatar/.gitignore | 0 background/.gitignore | 0 file/.gitignore | 0 plugins/ChooseTheme/ChooseThemePlugin.php | 74 ++ plugins/ChooseTheme/LICENSE | 662 ++++++++++++++++++ plugins/ChooseTheme/README.md | 15 + .../actions/choosethemesettings.php | 187 +++++ 7 files changed, 938 insertions(+) delete mode 100755 avatar/.gitignore delete mode 100644 background/.gitignore delete mode 100644 file/.gitignore create mode 100644 plugins/ChooseTheme/ChooseThemePlugin.php create mode 100644 plugins/ChooseTheme/LICENSE create mode 100644 plugins/ChooseTheme/README.md create mode 100644 plugins/ChooseTheme/actions/choosethemesettings.php diff --git a/avatar/.gitignore b/avatar/.gitignore deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/background/.gitignore b/background/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/file/.gitignore b/file/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/ChooseTheme/ChooseThemePlugin.php b/plugins/ChooseTheme/ChooseThemePlugin.php new file mode 100644 index 0000000000..b54e3d09a6 --- /dev/null +++ b/plugins/ChooseTheme/ChooseThemePlugin.php @@ -0,0 +1,74 @@ +. + * + * @author Knut Erik Hollund + * @copyright 2015 kollektivet0x242. http://www.kollektivet0x242.no + * + * @license GNU Affero General Public License http://www.gnu.org/licenses/ + */ + +class ChooseThemePlugin extends Plugin { + + public function onRouterInitialized(URLMapper $m) { + $m->connect('main/choosethemesettings', array('action' => 'choosethemesettings')); + } + + public function onPluginVersion(array &$versions) { + + $versions[] = array('name' => 'ChooseTheme', + 'version' => '0.1', + 'author' => 'Knut Erik "abjectio" Hollund', + 'homepage' => 'https://gitlab.com/kollektivet0x242/gsp-choosetheme', + 'rawdescription' => + // TRANS: Plugin description. + _m('Allowing user to select the preferred theme.')); + return true; + } + + /** + * Menu item for ChooseTheme + * + * @param Action $action action being executed + * + * @return boolean hook return + */ + function onEndAccountSettingsNav(Action $action) { + $action_name = $action->getActionName(); + + $action->menuItem(common_local_url('choosethemesettings'), + // TRANS: Poll plugin menu item on user settings page. + _m('MENU', 'Theme'), + // TRANS: Poll plugin tooltip for user settings menu item. + _m('Choose Theme'), + $action_name === 'themesettings'); + + return true; + } + + + function onStartShowStylesheets(Action $action) { + + //get the theme and set the current config for site and theme. + if($action->getScoped() instanceof Profile) { + $site_theme = common_config('site','theme'); + $user_theme = $action->getScoped()->getPref('chosen_theme', 'theme', $site_theme); + common_config_set('site', 'theme', $user_theme); + } + return true; + } +} diff --git a/plugins/ChooseTheme/LICENSE b/plugins/ChooseTheme/LICENSE new file mode 100644 index 0000000000..cebe0354b2 --- /dev/null +++ b/plugins/ChooseTheme/LICENSE @@ -0,0 +1,662 @@ +GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + diff --git a/plugins/ChooseTheme/README.md b/plugins/ChooseTheme/README.md new file mode 100644 index 0000000000..dd1cd4db51 --- /dev/null +++ b/plugins/ChooseTheme/README.md @@ -0,0 +1,15 @@ +### Choose theme +A simple plugin for [GNU social software](http://gnu.io/social/). +The plugin enables the user to select their own theme, independently on site setting and other users. +Consider this plugin as experimental. + +#### Enable plugin +- Include this code in your GNU social instance. +- Edit your `config.php` to include `addPlugin("ChooseTheme");` +(NB: Your plugin directory must then be named ChooseTheme or create a symbolic link to your cloned directory) + +#### How-to +- Choose settings from the GNU social menu. Choose 'Theme' on left menu. +- Select a theme and press 'Save'. + + diff --git a/plugins/ChooseTheme/actions/choosethemesettings.php b/plugins/ChooseTheme/actions/choosethemesettings.php new file mode 100644 index 0000000000..e284d8d231 --- /dev/null +++ b/plugins/ChooseTheme/actions/choosethemesettings.php @@ -0,0 +1,187 @@ +. + * + * @author Knut Erik Hollund + * @copyright 2015 kollektivet0x242. http://www.kollektivet0x242.no + * + * @license GNU Affero General Public License http://www.gnu.org/licenses/ + */ + +if (!defined('STATUSNET') && !defined('GNUSOCIAL')) { + exit(1); +} + +class ChooseThemeSettingsAction extends SettingsAction { + + + /** + * Title of the page + * @return string Page title + */ + function title() { + // TRANS: Page title. + return _m('Choose theme settings'); + } + + /** + * Instructions for use + * @return string Instructions for use + */ + function getInstructions() { + // TRANS: Page instructions. + return _m('Choose theme'); + } + + /** + * Show the form for ChooseTheme + * @return void + */ + function showContent() { + + $site_theme = common_config('site','theme'); + $prefs = $this->scoped->getPref('chosen_theme', 'theme',$site_theme); + if ($prefs === null) { + common_debug('No chosen theme found in database for user.'); + } + + //Get a list of available themes on instance + $available_themes = Theme::listAvailable(); + $chosenone = array_search($prefs,$available_themes,true); + $form = new ChooseThemeForm($this, $chosenone); + $form->show(); + } + + + /** + * Handler method + * + * @param array $argarray is ignored since it's now passed in in prepare() + * @return void + */ + function handlePost() { + + //Get a list of available themes on instance + $available_themes = Theme::listAvailable(); + $chosen_theme = $available_themes[(int)$this->arg('dwct','0')]; + + $this->success = true; + $this->msg = _('Settings saved.'); + + $this->success = $this->scoped->setPref('chosen_theme', 'theme', $chosen_theme); + // TRANS: Confirmation shown when user profile settings are saved. + if(!$this->success) $this->msg = _('No valid theme chosen.'); + + $this->showForm(_($this->msg), $this->success); + } +} + + +class ChooseThemeForm extends Form { + + protected $prefs = null; + + + function __construct($out, $prefs) { + parent::__construct($out); + + if ($prefs!=null) { + $this->prefs = $prefs; + } else { + $prefs = common_config('site','theme'); + } + +} + + /** + * Visible or invisible data elements + * + * Display the form fields that make up the data of the form. + * Sub-classes should overload this to show their data. + * @return void + */ + + function formData() { + + //Get a list of available themes on instance + $available_themes = Theme::listAvailable(); + + //Remove theme 'licenses' from selectable themes. + //The 'licenses' theme is not an actual theme and + //will just mess-up the gui. + $key = array_search('licenses',$available_themes); + if($key!=false){ + unset($available_themes[$key]); + } + + $this->elementStart('fieldset'); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->dropdown('dwct','Themes',$available_themes,'Select a theme',false, $this->prefs); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->elementEnd('fieldset'); + + } + + /** + * Buttons for form actions + * + * Submit and cancel buttons (or whatever) + * Sub-classes should overload this to show their own buttons. + * @return void + */ + + function formActions() + { + $this->submit('submit', _('Save')); + } + + /** + * ID of the form + * + * Should be unique on the page. Sub-classes should overload this + * to show their own IDs. + * @return int ID of the form + */ + + function id() { + return 'form_choosetheme_prefs'; + } + + /** + * Action of the form. + * + * URL to post to. Should be overloaded by subclasses to give + * somewhere to post to. + * @return string URL to post to + */ + + function action() { + return common_local_url('choosethemesettings'); + } + + /** + * Class of the form. May include space-separated list of multiple classes. + * + * @return string the form's class + */ + + function formClass() { + return 'form_settings'; + } +} From 8abf96cfd8f24df5834c2d8b79c5fc64eddafb47 Mon Sep 17 00:00:00 2001 From: abjectio Date: Sat, 6 Jun 2015 12:14:50 +0200 Subject: [PATCH 002/156] Edited the readme file to be more inline with the core repo --- plugins/ChooseTheme/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/ChooseTheme/README.md b/plugins/ChooseTheme/README.md index dd1cd4db51..2e0a761fa1 100644 --- a/plugins/ChooseTheme/README.md +++ b/plugins/ChooseTheme/README.md @@ -1,12 +1,10 @@ ### Choose theme A simple plugin for [GNU social software](http://gnu.io/social/). The plugin enables the user to select their own theme, independently on site setting and other users. -Consider this plugin as experimental. #### Enable plugin - Include this code in your GNU social instance. - Edit your `config.php` to include `addPlugin("ChooseTheme");` -(NB: Your plugin directory must then be named ChooseTheme or create a symbolic link to your cloned directory) #### How-to - Choose settings from the GNU social menu. Choose 'Theme' on left menu. From ea076a8783048f4297238081858e0425a075944f Mon Sep 17 00:00:00 2001 From: abjectio Date: Wed, 10 Jun 2015 22:10:14 +0200 Subject: [PATCH 003/156] Bug in use of gettext/locale string --- plugins/ChooseTheme/actions/choosethemesettings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ChooseTheme/actions/choosethemesettings.php b/plugins/ChooseTheme/actions/choosethemesettings.php index e284d8d231..97ef7229c9 100644 --- a/plugins/ChooseTheme/actions/choosethemesettings.php +++ b/plugins/ChooseTheme/actions/choosethemesettings.php @@ -80,7 +80,7 @@ class ChooseThemeSettingsAction extends SettingsAction { $chosen_theme = $available_themes[(int)$this->arg('dwct','0')]; $this->success = true; - $this->msg = _('Settings saved.'); + $this->msg = _m('Settings saved.'); $this->success = $this->scoped->setPref('chosen_theme', 'theme', $chosen_theme); // TRANS: Confirmation shown when user profile settings are saved. @@ -131,7 +131,7 @@ class ChooseThemeForm extends Form { $this->elementStart('fieldset'); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); - $this->dropdown('dwct','Themes',$available_themes,'Select a theme',false, $this->prefs); + $this->dropdown('dwct',_m('Themes'),$available_themes,_m('Select a theme'),false, $this->prefs); $this->elementEnd('li'); $this->elementEnd('ul'); $this->elementEnd('fieldset'); From 437dc3d713f9552ce982495e913b3411282c45c2 Mon Sep 17 00:00:00 2001 From: abjectio Date: Wed, 10 Jun 2015 22:11:34 +0200 Subject: [PATCH 004/156] Added .pot file and Norwegian translation --- plugins/ChooseTheme/locale/ChooseTheme.pot | 62 +++++++++++++++++++ .../locale/nb/LC_MESSAGES/ChooseTheme.po | 62 +++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 plugins/ChooseTheme/locale/ChooseTheme.pot create mode 100644 plugins/ChooseTheme/locale/nb/LC_MESSAGES/ChooseTheme.po diff --git a/plugins/ChooseTheme/locale/ChooseTheme.pot b/plugins/ChooseTheme/locale/ChooseTheme.pot new file mode 100644 index 0000000000..df6c0ca7cc --- /dev/null +++ b/plugins/ChooseTheme/locale/ChooseTheme.pot @@ -0,0 +1,62 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the GNU social package. +# FIRST AUTHOR abjectio@kollektivet0x242.no, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: Choose Theme\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-06-10 22:00+0100\n" +"PO-Revision-Date: 2015-06-10 22:01+0100\n" +"Last-Translator: Knut Erik Hollund \n" +"Language-Team: kollektivet0x242.no \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" +"X-Poedit-Basepath: ./actions\n" +"X-Poedit-KeywordsList: _m\n" +"X-Poedit-SearchPath-0: ./actions\n" +"X-Poedit-SearchPath-1: .\n" + +#: actions/choosethemesettings.php:38 +msgid "Choose theme settings" +msgstr "" + +#: actions/choosethemesettings.php:47 +msgid "Choose theme" +msgstr "" + +#: actions/choosethemesettings.php:83 +msgid "Settings saved." +msgstr "" + +#: actions/choosethemesettings.php:87 +msgid "No valid theme chosen." +msgstr "" + +#: actions/choosethemesettings.php:134 +msgid "Themes" +msgstr "" + +#: actions/choosethemesettings.php:134 +msgid "Select a theme" +msgstr "" + +#: actions/choosethemesettings.php:151 +msgid "Save" +msgstr "" + +#: ChooseThemePlugin.php:39 +msgid "Allowing user to select the preferred theme." +msgstr "" + +#: ChooseThemePlugin.php:55 +msgid "MENU" +msgstr "" + +#: ChooseThemePlugin.php:57 +msgid "Choose Theme" +msgstr "" diff --git a/plugins/ChooseTheme/locale/nb/LC_MESSAGES/ChooseTheme.po b/plugins/ChooseTheme/locale/nb/LC_MESSAGES/ChooseTheme.po new file mode 100644 index 0000000000..206a477185 --- /dev/null +++ b/plugins/ChooseTheme/locale/nb/LC_MESSAGES/ChooseTheme.po @@ -0,0 +1,62 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the GNU social package. +# FIRST AUTHOR abjectio@kollektivet0x242.no, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: Choose Theme\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-06-10 22:00+0100\n" +"PO-Revision-Date: 2015-06-10 22:04+0100\n" +"Last-Translator: Knut Erik Hollund \n" +"Language-Team: kollektivet0x242.no \n" +"Language: nb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" +"X-Poedit-Basepath: ./actions\n" +"X-Poedit-KeywordsList: _m\n" +"X-Poedit-SearchPath-0: ./actions\n" +"X-Poedit-SearchPath-1: .\n" + +#: actions/choosethemesettings.php:38 +msgid "Choose theme settings" +msgstr "Innstillinger" + +#: actions/choosethemesettings.php:47 +msgid "Choose theme" +msgstr "Velg tema" + +#: actions/choosethemesettings.php:83 +msgid "Settings saved." +msgstr "Innstillinger lagret." + +#: actions/choosethemesettings.php:87 +msgid "No valid theme chosen." +msgstr "Ingen gyldige tema er valgt." + +#: actions/choosethemesettings.php:134 +msgid "Themes" +msgstr "Temaer" + +#: actions/choosethemesettings.php:134 +msgid "Select a theme" +msgstr "Velg tema" + +#: actions/choosethemesettings.php:151 +msgid "Save" +msgstr "Lagre" + +#: ChooseThemePlugin.php:39 +msgid "Allowing user to select the preferred theme." +msgstr "Lar brukeren velge sitt foretrukne tema." + +#: ChooseThemePlugin.php:55 +msgid "MENU" +msgstr "Tema" + +#: ChooseThemePlugin.php:57 +msgid "Choose Theme" +msgstr "Velg tema" From e9a6d5be51b8d20d5c7a0e6345141d0006a6b65e Mon Sep 17 00:00:00 2001 From: abjectio Date: Sat, 13 Jun 2015 11:11:50 +0200 Subject: [PATCH 005/156] Removed the full AGPL license file --- plugins/ChooseTheme/LICENSE | 662 ------------------------------------ 1 file changed, 662 deletions(-) delete mode 100644 plugins/ChooseTheme/LICENSE diff --git a/plugins/ChooseTheme/LICENSE b/plugins/ChooseTheme/LICENSE deleted file mode 100644 index cebe0354b2..0000000000 --- a/plugins/ChooseTheme/LICENSE +++ /dev/null @@ -1,662 +0,0 @@ -GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. - From 9e1d4bfbf038c96e6e9e760ffc46f2f3a1cc6c37 Mon Sep 17 00:00:00 2001 From: Chimo Date: Sat, 20 Jun 2015 19:26:45 -0400 Subject: [PATCH 006/156] jquery-cookie returns `undefined` as of 1.4.0 when a cookie isn't set. This fixes a problem where the browser was never asking for location, and the JSON.parse call was throwing an exception when the geolocation cookie wasn't present. --- js/util.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/util.js b/js/util.js index b0274a91ba..9cde30ebf4 100644 --- a/js/util.js +++ b/js/util.js @@ -1081,12 +1081,12 @@ var SN = { // StatusNet label.attr('title', label.text()); check.change(function () { - if (check.prop('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === null) { + if (check.prop('checked') === true || $.cookie(SN.C.S.NoticeDataGeoCookie) === undefined) { label .attr('title', NoticeDataGeo_text.ShareDisable) .addClass('checked'); - if ($.cookie(SN.C.S.NoticeDataGeoCookie) === null || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') { + if ($.cookie(SN.C.S.NoticeDataGeoCookie) === undefined || $.cookie(SN.C.S.NoticeDataGeoCookie) == 'disabled') { if (navigator.geolocation) { SN.U.NoticeGeoStatus(form, 'Requesting location from browser...'); navigator.geolocation.getCurrentPosition( From a25d952ef9ab458b875e6a250d101178b8a582bb Mon Sep 17 00:00:00 2001 From: Chimo Date: Sat, 20 Jun 2015 19:35:43 -0400 Subject: [PATCH 007/156] $.cookie('n', null) -> $.removeCookie as of 1.4.0 --- js/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index 9cde30ebf4..cd982bbb17 100644 --- a/js/util.js +++ b/js/util.js @@ -1297,7 +1297,7 @@ var SN = { // StatusNet * @fixme what is this? */ Delete: function () { - $.cookie(SN.C.S.StatusNetInstance, null); + $.removeCookie(SN.C.S.StatusNetInstance); } }, From 62c6ed58ba0abcd4f454c059665f4adb0e854b54 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:18:45 +0200 Subject: [PATCH 008/156] DirectMessagePlugin actions modernified --- .../actions/apidirectmessage.php | 30 ++++++++----------- .../actions/apidirectmessagenew.php | 14 ++++----- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/plugins/DirectMessage/actions/apidirectmessage.php b/plugins/DirectMessage/actions/apidirectmessage.php index c18559f49d..17c445c276 100644 --- a/plugins/DirectMessage/actions/apidirectmessage.php +++ b/plugins/DirectMessage/actions/apidirectmessage.php @@ -30,9 +30,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Show a list of direct messages from or to the authenticating user @@ -62,13 +60,11 @@ class ApiDirectMessageAction extends ApiAuthAction * * @return boolean success flag */ - function prepare($args) + protected function prepare(array $args=array()) { parent::prepare($args); - $this->user = $this->auth_user; - - if (empty($this->user)) { + if (!$this->scoped instanceof Profile) { // TRANS: Client error given when a user was not found (404). $this->clientError(_('No such user.'), 404); } @@ -83,30 +79,30 @@ class ApiDirectMessageAction extends ApiAuthAction $this->title = sprintf( // TRANS: Title. %s is a user nickname. _("Direct messages from %s"), - $this->user->nickname + $this->scoped->getNickname() ); $this->subtitle = sprintf( // TRANS: Subtitle. %s is a user nickname. _("All the direct messages sent from %s"), - $this->user->nickname + $this->scoped->getNickname() ); - $this->link = $server . $this->user->nickname . '/outbox'; + $this->link = $server . $this->scoped->getNickname() . '/outbox'; $this->selfuri_base = common_root_url() . 'api/direct_messages/sent'; - $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id; + $this->id = "tag:$taguribase:SentDirectMessages:" . $this->scoped->getID(); } else { $this->title = sprintf( // TRANS: Title. %s is a user nickname. _("Direct messages to %s"), - $this->user->nickname + $this->scoped->getNickname() ); $this->subtitle = sprintf( // TRANS: Subtitle. %s is a user nickname. _("All the direct messages sent to %s"), - $this->user->nickname + $this->scoped->getNickname() ); - $this->link = $server . $this->user->nickname . '/inbox'; + $this->link = $server . $this->scoped->getNickname() . '/inbox'; $this->selfuri_base = common_root_url() . 'api/direct_messages'; - $this->id = "tag:$taguribase:DirectMessages:" . $this->user->id; + $this->id = "tag:$taguribase:DirectMessages:" . $this->scoped->getID(); } $this->messages = $this->getMessages(); @@ -166,9 +162,9 @@ class ApiDirectMessageAction extends ApiAuthAction $message = new Message(); if ($this->arg('sent')) { - $message->from_profile = $this->user->id; + $message->from_profile = $this->scoped->getID(); } else { - $message->to_profile = $this->user->id; + $message->to_profile = $this->scoped->getID(); } if (!empty($this->max_id)) { diff --git a/plugins/DirectMessage/actions/apidirectmessagenew.php b/plugins/DirectMessage/actions/apidirectmessagenew.php index 653fa3a9ed..e2c7ab5ca7 100644 --- a/plugins/DirectMessage/actions/apidirectmessagenew.php +++ b/plugins/DirectMessage/actions/apidirectmessagenew.php @@ -30,9 +30,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Creates a new direct message from the authenticating user to @@ -65,7 +63,7 @@ class ApiDirectMessageNewAction extends ApiAuthAction { parent::prepare($args); - if (empty($this->user)) { + if (!$this->scoped instanceof Profile) { // TRANS: Client error when user not found for an API direct message action. $this->clientError(_('No such user.'), 404); } @@ -111,10 +109,10 @@ class ApiDirectMessageNewAction extends ApiAuthAction if (!$this->other instanceof Profile) { // TRANS: Client error displayed if a recipient user could not be found (403). $this->clientError(_('Recipient user not found.'), 403); - } else if (!$this->user->mutuallySubscribed($this->other)) { + } else if (!$this->scoped->mutuallySubscribed($this->other)) { // TRANS: Client error displayed trying to direct message another user who's not a friend (403). $this->clientError(_('Cannot send direct messages to users who aren\'t your friend.'), 403); - } else if ($this->user->id == $this->other->id) { + } else if ($this->scoped->getID() === $this->other->getID()) { // Note: sending msgs to yourself is allowed by Twitter @@ -123,8 +121,8 @@ class ApiDirectMessageNewAction extends ApiAuthAction } $message = Message::saveNew( - $this->user->id, - $this->other->id, + $this->scoped->getID(), + $this->other->getID(), html_entity_decode($this->content, ENT_NOQUOTES, 'UTF-8'), $this->source ); From edef6f929a31b5662441364db2452aa98c2bf910 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:30:14 +0200 Subject: [PATCH 009/156] Don't statically call Validate functions --- lib/activityutils.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/activityutils.php b/lib/activityutils.php index d4c01232ec..f3383efda5 100644 --- a/lib/activityutils.php +++ b/lib/activityutils.php @@ -281,19 +281,20 @@ class ActivityUtils static function validateUri($uri) { // Check mailto: URIs first + $validate = new Validate(); if (preg_match('/^mailto:(.*)$/', $uri, $match)) { - return Validate::email($match[1], common_config('email', 'check_domain')); + return $validate->email($match[1], common_config('email', 'check_domain')); } - if (Validate::uri($uri)) { + if ($validate->uri($uri)) { return true; } // Possibly an upstream bug; tag: URIs aren't validated properly // unless you explicitly ask for them. All other schemes are accepted // for basic URI validation without asking. - if (Validate::uri($uri, array('allowed_scheme' => array('tag')))) { + if ($validate->uri($uri, array('allowed_scheme' => array('tag')))) { return true; } From acdcb2ad8d5b68d7405b8250faf8c80977934f16 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:34:42 +0200 Subject: [PATCH 010/156] prepare and handle function to match parents --- actions/userrss.php | 4 ++-- lib/rssaction.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actions/userrss.php b/actions/userrss.php index 308db94891..fe4ae155a3 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } require_once(INSTALLDIR.'/lib/rssaction.php'); @@ -27,7 +27,7 @@ class UserrssAction extends Rss10Action { var $tag = null; - function prepare($args) + protected function prepare(array $args=array()) { parent::prepare($args); $nickname = $this->trimmed('nickname'); diff --git a/lib/rssaction.php b/lib/rssaction.php index c3e1283fed..bb4fa12b74 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -75,7 +75,7 @@ class Rss10Action extends Action * @return boolean success */ - function prepare($args) + protected function prepare(array $args=array()) { parent::prepare($args); @@ -120,10 +120,10 @@ class Rss10Action extends Action * @return void */ - function handle($args) + protected function handle() { // Parent handling, including cache check - parent::handle($args); + parent::handle(); $this->showRss(); } From 6919dda958d2cf5cbc7bd054121a95eb22851469 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:45:01 +0200 Subject: [PATCH 011/156] RobotstxtAction migrated to ManagedAction --- actions/robotstxt.php | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/actions/robotstxt.php b/actions/robotstxt.php index d686042cb1..fdfc2bd1bd 100644 --- a/actions/robotstxt.php +++ b/actions/robotstxt.php @@ -27,9 +27,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Prints out a static robots.txt @@ -40,19 +38,9 @@ if (!defined('STATUSNET')) { * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ */ -class RobotstxtAction extends Action +class RobotstxtAction extends ManagedAction { - /** - * Handles requests - * - * Since this is a relatively static document, we - * don't do a prepare() - * - * @param array $args GET, POST, and URL params; unused. - * - * @return void - */ - function handle($args) + public function showPage() { if (Event::handle('StartRobotsTxt', array($this))) { From 45ee2060fa9317e65ea4ea09084bf8158c10ee61 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:48:18 +0200 Subject: [PATCH 012/156] File_redirection is called statically --- classes/File_redirection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/File_redirection.php b/classes/File_redirection.php index ba516e4623..e0d68b7330 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -166,7 +166,7 @@ class File_redirection extends Managed_DataObject * size (optional): byte size from Content-Length header * time (optional): timestamp from Last-Modified header */ - public function where($in_url, $discover=true) { + static function where($in_url, $discover=true) { // let's see if we know this... try { $a = File::getByUrl($in_url); From d0458b824a18c4e5c18a3c00ed39e96e695cda82 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 19:59:43 +0200 Subject: [PATCH 013/156] File_redirection minor coding layout fix --- classes/File_redirection.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/File_redirection.php b/classes/File_redirection.php index e0d68b7330..75a3b577ae 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -176,7 +176,7 @@ class File_redirection extends Managed_DataObject try { $b = File_redirection::getByUrl($in_url); // this is a redirect to $b->file_id - $a = File::getKV('id', $b->file_id); + $a = File::getByID($b->file_id); return $a->url; } catch (NoResultException $e) { // Oh well, let's keep going @@ -186,10 +186,10 @@ class File_redirection extends Managed_DataObject if ($discover) { $ret = File_redirection::lookupWhere($in_url); return $ret; - } else { - // No manual dereferencing; leave the unknown URL as is. - return $in_url; } + + // No manual dereferencing; leave the unknown URL as is. + return $in_url; } /** From 33dc06ae21daf9a98fd3c44c0eddcd02e0a67fc6 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 20:02:41 +0200 Subject: [PATCH 014/156] handle function declaration to match parent --- plugins/DirectMessage/actions/apidirectmessage.php | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/plugins/DirectMessage/actions/apidirectmessage.php b/plugins/DirectMessage/actions/apidirectmessage.php index 17c445c276..8e7f6f8061 100644 --- a/plugins/DirectMessage/actions/apidirectmessage.php +++ b/plugins/DirectMessage/actions/apidirectmessage.php @@ -110,18 +110,9 @@ class ApiDirectMessageAction extends ApiAuthAction return true; } - /** - * Handle the request - * - * Show the messages - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - function handle($args) + protected function handle() { - parent::handle($args); + parent::handle(); $this->showMessages(); } From 77957372abcd7e984c73ed7946190343cc269b53 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 7 Jul 2015 20:16:05 +0200 Subject: [PATCH 015/156] File_redirection static fixes and simplifying --- classes/File_redirection.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 75a3b577ae..2b6f86ed27 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -206,7 +206,7 @@ class File_redirection extends Managed_DataObject * @param User $user whose shortening options to use; defaults to the current web session user * @return string */ - function makeShort($long_url, $user=null) + static function makeShort($long_url, $user=null) { $canon = File_redirection::_canonUrl($long_url); @@ -214,11 +214,7 @@ class File_redirection extends Managed_DataObject // Did we get one? Is it shorter? - if (!empty($short_url)) { - return $short_url; - } else { - return $long_url; - } + return !empty($short_url) ? $short_url : $long_url; } /** @@ -235,18 +231,14 @@ class File_redirection extends Managed_DataObject * @return string */ - function forceShort($long_url, $user) + static function forceShort($long_url, $user) { $canon = File_redirection::_canonUrl($long_url); $short_url = File_redirection::_userMakeShort($canon, $user, true); // Did we get one? Is it shorter? - if (!empty($short_url)) { - return $short_url; - } else { - return $long_url; - } + return !empty($short_url) ? $short_url : $long_url; } static function _userMakeShort($long_url, User $user=null, $force = false) { From f939485b52eb9c31365438bcc1fa9fcc9ea0154f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 8 Jul 2015 07:57:27 +0200 Subject: [PATCH 016/156] Gravatar relies on closed source 3rd party --- plugins/Gravatar/GravatarPlugin.php | 74 ------------------- plugins/Gravatar/README | 12 --- plugins/Gravatar/locale/Gravatar.pot | 25 ------- .../locale/af/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ar/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/arz/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ast/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/be-tarask/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/bg/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/br/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ca/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/cs/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/da/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/de/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/el/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/en_GB/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/eo/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/es/LC_MESSAGES/Gravatar.po | 26 ------- .../locale/eu/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/fa/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/fi/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/fr/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/fur/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/gl/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/he/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/hsb/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/hu/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ia/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/id/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/is/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/it/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ja/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ka/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ko/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ksh/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/lb/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/lt/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/lv/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/mg/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/mk/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ml/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ms/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/my/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/nb/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ne/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/nl/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/nn/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/pl/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/pt/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/pt_BR/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ru/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/sr-ec/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/sv/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ta/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/te/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/tl/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/tr/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/uk/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/ur_PK/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/vi/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/zh_CN/LC_MESSAGES/Gravatar.po | 25 ------- .../locale/zh_TW/LC_MESSAGES/Gravatar.po | 25 ------- 62 files changed, 1587 deletions(-) delete mode 100644 plugins/Gravatar/GravatarPlugin.php delete mode 100644 plugins/Gravatar/README delete mode 100644 plugins/Gravatar/locale/Gravatar.pot delete mode 100644 plugins/Gravatar/locale/af/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ar/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/arz/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ast/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/be-tarask/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/bg/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/br/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ca/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/cs/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/da/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/de/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/el/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/en_GB/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/eo/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/es/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/eu/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/fa/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/fi/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/fr/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/fur/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/gl/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/he/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/hsb/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/hu/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ia/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/id/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/is/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/it/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ja/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ka/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ko/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ksh/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/lb/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/lt/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/lv/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/mg/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/mk/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ml/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ms/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/my/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/nb/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ne/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/nl/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/nn/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/pl/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/pt/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/pt_BR/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ru/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/sr-ec/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/sv/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ta/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/te/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/tl/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/tr/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/uk/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/ur_PK/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/vi/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/zh_CN/LC_MESSAGES/Gravatar.po delete mode 100644 plugins/Gravatar/locale/zh_TW/LC_MESSAGES/Gravatar.po diff --git a/plugins/Gravatar/GravatarPlugin.php b/plugins/Gravatar/GravatarPlugin.php deleted file mode 100644 index cf9e3a01fe..0000000000 --- a/plugins/Gravatar/GravatarPlugin.php +++ /dev/null @@ -1,74 +0,0 @@ -. - */ - -/** - * @package GravatarPlugin - * @maintainer Eric Helgeson - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -class GravatarPlugin extends Plugin -{ - function onEndProfileGetAvatar($profile, $size, &$avatar) - { - if (empty($avatar)) { - try { - $user = $profile->getUser(); - if (!empty($user->email)) { - // Fake one! - $avatar = new Avatar(); - $avatar->width = $avatar->height = $size; - $avatar->url = $this->gravatar_url($user->email, $size); - return false; - } - } catch (NoSuchUserException $e) { - return true; - } - } - - return true; - } - - function gravatar_url($email, $size) - { - $url = "https://secure.gravatar.com/avatar.php?gravatar_id=". - md5(strtolower($email)). - "&default=".urlencode(Avatar::defaultImage($size)). - "&size=".$size; - return $url; - } - - function onPluginVersion(array &$versions) - { - $versions[] = array('name' => 'Gravatar', - 'version' => GNUSOCIAL_VERSION, - 'author' => 'Eric Helgeson, Evan Prodromou', - 'homepage' => 'http://status.net/wiki/Plugin:Gravatar', - 'rawdescription' => - // TRANS: Plugin decsription. - _m('The Gravatar plugin allows users to use their Gravatar with StatusNet.')); - - return true; - } -} diff --git a/plugins/Gravatar/README b/plugins/Gravatar/README deleted file mode 100644 index 0f6c7280d9..0000000000 --- a/plugins/Gravatar/README +++ /dev/null @@ -1,12 +0,0 @@ -GravatarPlugin 0.1 - -About: -This will allow users to use their Gravatar Avatar with your StatusNet install. - -Configuration: -add this to your config.php: -addPlugin('Gravatar', array()); - -To do: -Site default all on for gravatar by default -Migration Script diff --git a/plugins/Gravatar/locale/Gravatar.pot b/plugins/Gravatar/locale/Gravatar.pot deleted file mode 100644 index 01bcc5d69e..0000000000 --- a/plugins/Gravatar/locale/Gravatar.pot +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-27 16:31+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" -"Content-Transfer-Encoding: 8bit\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/af/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/af/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 0f97b86afc..0000000000 --- a/plugins/Gravatar/locale/af/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Afrikaans (http://www.transifex.com/projects/p/gnu-social/language/af/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: af\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ar/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ar/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 0ffd7f7b20..0000000000 --- a/plugins/Gravatar/locale/ar/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Arabic (http://www.transifex.com/projects/p/gnu-social/language/ar/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ar\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/arz/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/arz/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 86ede50f6a..0000000000 --- a/plugins/Gravatar/locale/arz/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Arabic (Egypt) (http://www.transifex.com/projects/p/gnu-social/language/ar_EG/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ar_EG\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ast/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ast/LC_MESSAGES/Gravatar.po deleted file mode 100644 index fe73afad4e..0000000000 --- a/plugins/Gravatar/locale/ast/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Asturian (http://www.transifex.com/projects/p/gnu-social/language/ast/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ast\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/be-tarask/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/be-tarask/LC_MESSAGES/Gravatar.po deleted file mode 100644 index b495ade1d3..0000000000 --- a/plugins/Gravatar/locale/be-tarask/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Belarusian (Tarask) (http://www.transifex.com/projects/p/gnu-social/language/be@tarask/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: be@tarask\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/bg/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/bg/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 7cd09ef12a..0000000000 --- a/plugins/Gravatar/locale/bg/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Bulgarian (http://www.transifex.com/projects/p/gnu-social/language/bg/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: bg\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/br/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/br/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 92ab60af5f..0000000000 --- a/plugins/Gravatar/locale/br/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Breton (http://www.transifex.com/projects/p/gnu-social/language/br/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: br\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ca/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ca/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 1191b6865d..0000000000 --- a/plugins/Gravatar/locale/ca/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Catalan (http://www.transifex.com/projects/p/gnu-social/language/ca/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ca\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "El connector del Gravatar permet als usuaris fer servir llur Gravatar amb l'StatusNet." diff --git a/plugins/Gravatar/locale/cs/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/cs/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 2ce5576e7b..0000000000 --- a/plugins/Gravatar/locale/cs/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Czech (http://www.transifex.com/projects/p/gnu-social/language/cs/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/da/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/da/LC_MESSAGES/Gravatar.po deleted file mode 100644 index d47b755da9..0000000000 --- a/plugins/Gravatar/locale/da/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Danish (http://www.transifex.com/projects/p/gnu-social/language/da/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: da\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/de/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/de/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 625c20c533..0000000000 --- a/plugins/Gravatar/locale/de/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: German (http://www.transifex.com/projects/p/gnu-social/language/de/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Das Gravatar-Plugin erlaubt es Benutzern, ihr Gravatar mit StatusNet zu verwenden." diff --git a/plugins/Gravatar/locale/el/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/el/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 021657de04..0000000000 --- a/plugins/Gravatar/locale/el/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Greek (http://www.transifex.com/projects/p/gnu-social/language/el/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: el\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/en_GB/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/en_GB/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 7bc45a1f9f..0000000000 --- a/plugins/Gravatar/locale/en_GB/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/gnu-social/language/en_GB/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: en_GB\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/eo/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/eo/LC_MESSAGES/Gravatar.po deleted file mode 100644 index a955db7931..0000000000 --- a/plugins/Gravatar/locale/eo/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Esperanto (http://www.transifex.com/projects/p/gnu-social/language/eo/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: eo\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/es/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/es/LC_MESSAGES/Gravatar.po deleted file mode 100644 index b289d4f47f..0000000000 --- a/plugins/Gravatar/locale/es/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,26 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -# Juan Riquelme González , 2015 -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-27 12:21+0000\n" -"Last-Translator: Juan Riquelme González \n" -"Language-Team: Spanish (http://www.transifex.com/projects/p/gnu-social/language/es/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "El complemento Gravatar permite a los usuarios utilizar su Gravatar en GNU social." diff --git a/plugins/Gravatar/locale/eu/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/eu/LC_MESSAGES/Gravatar.po deleted file mode 100644 index bb87408ddd..0000000000 --- a/plugins/Gravatar/locale/eu/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Basque (http://www.transifex.com/projects/p/gnu-social/language/eu/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: eu\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Gravatar pluginak erabiltzaileei heuren Gravatarra StatusNet-en erabiltzen uzten die." diff --git a/plugins/Gravatar/locale/fa/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/fa/LC_MESSAGES/Gravatar.po deleted file mode 100644 index c7ee433734..0000000000 --- a/plugins/Gravatar/locale/fa/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Persian (http://www.transifex.com/projects/p/gnu-social/language/fa/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fa\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/fi/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/fi/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 7b0e4e1c44..0000000000 --- a/plugins/Gravatar/locale/fi/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Finnish (http://www.transifex.com/projects/p/gnu-social/language/fi/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fi\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/fr/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/fr/LC_MESSAGES/Gravatar.po deleted file mode 100644 index c24e581db1..0000000000 --- a/plugins/Gravatar/locale/fr/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: French (http://www.transifex.com/projects/p/gnu-social/language/fr/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Le greffon Gravatar permet aux utilisateurs d’utiliser leur image Gravatar avec StatusNet." diff --git a/plugins/Gravatar/locale/fur/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/fur/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 3cd3fdfa88..0000000000 --- a/plugins/Gravatar/locale/fur/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Friulian (http://www.transifex.com/projects/p/gnu-social/language/fur/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fur\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/gl/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/gl/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 629918e81f..0000000000 --- a/plugins/Gravatar/locale/gl/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Galician (http://www.transifex.com/projects/p/gnu-social/language/gl/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: gl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "O complemento Gravatar permite aos usuarios usar o seu Gravatar co StatusNet." diff --git a/plugins/Gravatar/locale/he/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/he/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 475f35cefb..0000000000 --- a/plugins/Gravatar/locale/he/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Hebrew (http://www.transifex.com/projects/p/gnu-social/language/he/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "תוסף Gravatar מאפשר למשתמשים להציג את ה־Gravatar שלהם בסטטוסנט." diff --git a/plugins/Gravatar/locale/hsb/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/hsb/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 63410e7686..0000000000 --- a/plugins/Gravatar/locale/hsb/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Upper Sorbian (http://www.transifex.com/projects/p/gnu-social/language/hsb/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: hsb\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/hu/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/hu/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 16c5f1b3e1..0000000000 --- a/plugins/Gravatar/locale/hu/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Hungarian (http://www.transifex.com/projects/p/gnu-social/language/hu/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: hu\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ia/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ia/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 63ffdb969c..0000000000 --- a/plugins/Gravatar/locale/ia/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Interlingua (http://www.transifex.com/projects/p/gnu-social/language/ia/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ia\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Le plug-in Gravatar permitte al usatores de usar lor Gravatar con StatusNet." diff --git a/plugins/Gravatar/locale/id/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/id/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 3c2b453d5e..0000000000 --- a/plugins/Gravatar/locale/id/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Indonesian (http://www.transifex.com/projects/p/gnu-social/language/id/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: id\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/is/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/is/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 3da39d3ca5..0000000000 --- a/plugins/Gravatar/locale/is/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Icelandic (http://www.transifex.com/projects/p/gnu-social/language/is/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: is\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/it/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/it/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 5793afcb7b..0000000000 --- a/plugins/Gravatar/locale/it/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Italian (http://www.transifex.com/projects/p/gnu-social/language/it/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: it\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Il plugin Gravatar consente agli utenti di utilizzare i loro Gravatar con StatusNet." diff --git a/plugins/Gravatar/locale/ja/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ja/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 53263e5f95..0000000000 --- a/plugins/Gravatar/locale/ja/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Japanese (http://www.transifex.com/projects/p/gnu-social/language/ja/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ja\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ka/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ka/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 19511461c8..0000000000 --- a/plugins/Gravatar/locale/ka/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Georgian (http://www.transifex.com/projects/p/gnu-social/language/ka/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ka\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ko/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ko/LC_MESSAGES/Gravatar.po deleted file mode 100644 index cde819b26d..0000000000 --- a/plugins/Gravatar/locale/ko/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Korean (http://www.transifex.com/projects/p/gnu-social/language/ko/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ko\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ksh/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ksh/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 2212303688..0000000000 --- a/plugins/Gravatar/locale/ksh/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Colognian (http://www.transifex.com/projects/p/gnu-social/language/ksh/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ksh\n" -"Plural-Forms: nplurals=3; plural=(n==0) ? 0 : (n==1) ? 1 : 2;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/lb/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/lb/LC_MESSAGES/Gravatar.po deleted file mode 100644 index dd0a27f64b..0000000000 --- a/plugins/Gravatar/locale/lb/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Luxembourgish (http://www.transifex.com/projects/p/gnu-social/language/lb/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: lb\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/lt/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/lt/LC_MESSAGES/Gravatar.po deleted file mode 100644 index be4f8879d9..0000000000 --- a/plugins/Gravatar/locale/lt/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Lithuanian (http://www.transifex.com/projects/p/gnu-social/language/lt/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: lt\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/lv/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/lv/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 370e8f7f4d..0000000000 --- a/plugins/Gravatar/locale/lv/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-07 09:39+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Latvian (http://www.transifex.com/projects/p/gnu-social/language/lv/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: lv\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/mg/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/mg/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 1f3889e434..0000000000 --- a/plugins/Gravatar/locale/mg/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Malagasy (http://www.transifex.com/projects/p/gnu-social/language/mg/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: mg\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/mk/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/mk/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 14015f3948..0000000000 --- a/plugins/Gravatar/locale/mk/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Macedonian (http://www.transifex.com/projects/p/gnu-social/language/mk/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: mk\n" -"Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Приклучокот Gravatar им овозможува на корисниците да го користат својот Gravatar со StatusNet." diff --git a/plugins/Gravatar/locale/ml/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ml/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 71920fd8dc..0000000000 --- a/plugins/Gravatar/locale/ml/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Malayalam (http://www.transifex.com/projects/p/gnu-social/language/ml/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ml\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ms/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ms/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 2bcaa63925..0000000000 --- a/plugins/Gravatar/locale/ms/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Malay (http://www.transifex.com/projects/p/gnu-social/language/ms/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ms\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/my/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/my/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 2bd1d77cd5..0000000000 --- a/plugins/Gravatar/locale/my/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Burmese (http://www.transifex.com/projects/p/gnu-social/language/my/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: my\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/nb/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/nb/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 5bfca90a5f..0000000000 --- a/plugins/Gravatar/locale/nb/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Norwegian Bokmål (http://www.transifex.com/projects/p/gnu-social/language/nb/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: nb\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ne/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ne/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 0a8a3f54eb..0000000000 --- a/plugins/Gravatar/locale/ne/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-07 09:30+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Nepali (http://www.transifex.com/projects/p/gnu-social/language/ne/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ne\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/nl/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/nl/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 39339a349b..0000000000 --- a/plugins/Gravatar/locale/nl/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Dutch (http://www.transifex.com/projects/p/gnu-social/language/nl/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: nl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "De plug-in Gravatar maak het mogelijk dat gebruikers hun Gravatar gebruiken in StatusNet." diff --git a/plugins/Gravatar/locale/nn/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/nn/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 6c7a1ecd8e..0000000000 --- a/plugins/Gravatar/locale/nn/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Norwegian Nynorsk (http://www.transifex.com/projects/p/gnu-social/language/nn/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: nn\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/pl/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/pl/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 4bf9e1ab4a..0000000000 --- a/plugins/Gravatar/locale/pl/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Polish (http://www.transifex.com/projects/p/gnu-social/language/pl/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Wtyczka Gravatar umożliwia użytkownikom używanie obrazów Gravatar w StatusNet." diff --git a/plugins/Gravatar/locale/pt/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/pt/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 50b36f6d3e..0000000000 --- a/plugins/Gravatar/locale/pt/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Portuguese (http://www.transifex.com/projects/p/gnu-social/language/pt/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: pt\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "O plugin Gravatar permite que os utilizadores usem o seu Gravatar com o StatusNet." diff --git a/plugins/Gravatar/locale/pt_BR/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/pt_BR/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 3bd93e4ceb..0000000000 --- a/plugins/Gravatar/locale/pt_BR/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/gnu-social/language/pt_BR/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: pt_BR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/ru/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ru/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 1f304ef9ca..0000000000 --- a/plugins/Gravatar/locale/ru/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Russian (http://www.transifex.com/projects/p/gnu-social/language/ru/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ru\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/sr-ec/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/sr-ec/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 32a78b636a..0000000000 --- a/plugins/Gravatar/locale/sr-ec/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Serbian (http://www.transifex.com/projects/p/gnu-social/language/sr/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sr\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/sv/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/sv/LC_MESSAGES/Gravatar.po deleted file mode 100644 index a3f459f741..0000000000 --- a/plugins/Gravatar/locale/sv/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Swedish (http://www.transifex.com/projects/p/gnu-social/language/sv/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: sv\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Gravatar-tillägget låter användare använda deras Gravatar med StatusNet." diff --git a/plugins/Gravatar/locale/ta/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ta/LC_MESSAGES/Gravatar.po deleted file mode 100644 index c5b05969b4..0000000000 --- a/plugins/Gravatar/locale/ta/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-07 08:48+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Tamil (http://www.transifex.com/projects/p/gnu-social/language/ta/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ta\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/te/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/te/LC_MESSAGES/Gravatar.po deleted file mode 100644 index f842abd66d..0000000000 --- a/plugins/Gravatar/locale/te/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Telugu (http://www.transifex.com/projects/p/gnu-social/language/te/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: te\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/tl/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/tl/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 76efd0d06f..0000000000 --- a/plugins/Gravatar/locale/tl/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Tagalog (http://www.transifex.com/projects/p/gnu-social/language/tl/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: tl\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Ang pamasak na Gravatar ay nagpapahintulot sa mga tagagamit na gamitin ang kanilang Gravatar na may StatusNet." diff --git a/plugins/Gravatar/locale/tr/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/tr/LC_MESSAGES/Gravatar.po deleted file mode 100644 index afd815239e..0000000000 --- a/plugins/Gravatar/locale/tr/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Turkish (http://www.transifex.com/projects/p/gnu-social/language/tr/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: tr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/uk/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/uk/LC_MESSAGES/Gravatar.po deleted file mode 100644 index c84ec1db82..0000000000 --- a/plugins/Gravatar/locale/uk/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Ukrainian (http://www.transifex.com/projects/p/gnu-social/language/uk/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: uk\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Додаток Gravatar дозволяє користувачам встановлювати аватарки з Gravatar для сайту StatusNet." diff --git a/plugins/Gravatar/locale/ur_PK/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/ur_PK/LC_MESSAGES/Gravatar.po deleted file mode 100644 index f7de61f290..0000000000 --- a/plugins/Gravatar/locale/ur_PK/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Urdu (Pakistan) (http://www.transifex.com/projects/p/gnu-social/language/ur_PK/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ur_PK\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/vi/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/vi/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 0cdfdcc657..0000000000 --- a/plugins/Gravatar/locale/vi/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Vietnamese (http://www.transifex.com/projects/p/gnu-social/language/vi/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: vi\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" diff --git a/plugins/Gravatar/locale/zh_CN/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/zh_CN/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 8a2a5c68bd..0000000000 --- a/plugins/Gravatar/locale/zh_CN/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:43+0000\n" -"Last-Translator: digitaldreamer \n" -"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/gnu-social/language/zh_CN/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_CN\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "Gravatar 插件可以让用户在 StatusNet 站点使用自己的 Gravatar。" diff --git a/plugins/Gravatar/locale/zh_TW/LC_MESSAGES/Gravatar.po b/plugins/Gravatar/locale/zh_TW/LC_MESSAGES/Gravatar.po deleted file mode 100644 index 2a603fff53..0000000000 --- a/plugins/Gravatar/locale/zh_TW/LC_MESSAGES/Gravatar.po +++ /dev/null @@ -1,25 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# -# Translators: -msgid "" -msgstr "" -"Project-Id-Version: GNU social\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-02-02 17:47+0100\n" -"PO-Revision-Date: 2015-02-06 16:27+0000\n" -"Last-Translator: FULL NAME \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/gnu-social/language/zh_TW/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: zh_TW\n" -"Plural-Forms: nplurals=1; plural=0;\n" - -#. TRANS: Plugin decsription. -#: GravatarPlugin.php:70 -msgid "" -"The Gravatar plugin allows users to use their Gravatar with StatusNet." -msgstr "" From 2103075ffaa94d56549a2b34fc0d266ff91d3603 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 8 Jul 2015 12:30:21 +0200 Subject: [PATCH 017/156] Delete notice script added. Give an ID or URI. --- scripts/delete_notice.php | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100755 scripts/delete_notice.php diff --git a/scripts/delete_notice.php b/scripts/delete_notice.php new file mode 100755 index 0000000000..bf10cbb2b2 --- /dev/null +++ b/scripts/delete_notice.php @@ -0,0 +1,68 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::n::u::y'; +$longoptions = array('id=', 'nickname=', 'uri=', 'yes'); + +$helptext = <<getID()." by '".$notice->getProfile()->getNickname()."'. Are you sure? [y/N] "; + $response = fgets(STDIN); + if (strtolower(trim($response)) != 'y') { + print "Aborting.\n"; + exit(0); + } +} + +print "Deleting..."; +$notice->delete(); +print "DONE.\n"; From 43cec8eaac878ee1b65d318ed44cb52af2b97e15 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 13:46:03 +0200 Subject: [PATCH 018/156] API actions are not ManagedAction yet --- actions/apitimelinetag.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/actions/apitimelinetag.php b/actions/apitimelinetag.php index 04a4727a9d..1184440c7d 100644 --- a/actions/apitimelinetag.php +++ b/actions/apitimelinetag.php @@ -51,10 +51,14 @@ class ApiTimelineTagAction extends ApiPrivateAuthAction { var $notices = null; - protected function doPreparation() + protected function prepare(array $args=array()) { + parent::prepare($args); + $this->tag = $this->arg('tag'); $this->notices = $this->getNotices(); + + return true; } /** From 65f1f74f2ba7d4e8e728e45d5f21b242816d532a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 13:56:02 +0200 Subject: [PATCH 019/156] Type controlling in lib/atomnoticefeed.php --- lib/atomnoticefeed.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/atomnoticefeed.php b/lib/atomnoticefeed.php index 292bc97e85..b7e1ed1b41 100644 --- a/lib/atomnoticefeed.php +++ b/lib/atomnoticefeed.php @@ -113,10 +113,12 @@ class AtomNoticeFeed extends Atom10Feed foreach ($notices as $notice) { $this->addEntryFromNotice($notice); } - } else { + } elseif ($notices instanceof Notice) { while ($notices->fetch()) { $this->addEntryFromNotice($notices); } + } else { + throw new ServerException('addEntryFromNotices got neither an array nor a Notice object'); } } @@ -125,7 +127,7 @@ class AtomNoticeFeed extends Atom10Feed * * @param Notice $notice a Notice to add */ - function addEntryFromNotice($notice) + function addEntryFromNotice(Notice $notice) { try { $source = $this->showSource(); From 9b8ac2d4045791c36f6241a276a4ae253c7f4fe2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 14:04:07 +0200 Subject: [PATCH 020/156] CSS can handle alternating row colouring now --- plugins/Directory/css/directory.css | 6 +-- .../lib/sortablesubscriptionlist.php | 40 ++----------------- theme/base/css/display.css | 5 +++ theme/neo-gnu/css/display.css | 5 --- theme/neo/css/display.css | 5 --- 5 files changed, 10 insertions(+), 51 deletions(-) diff --git a/plugins/Directory/css/directory.css b/plugins/Directory/css/directory.css index d49c28fe55..b4fe68d4cc 100644 --- a/plugins/Directory/css/directory.css +++ b/plugins/Directory/css/directory.css @@ -41,10 +41,6 @@ table.profile_list tr { float: none; } -table.profile_list tr.alt { - background-color: #def; /* zebra stripe */ -} - table.profie_list td { width: 100%; padding: 0; @@ -61,4 +57,4 @@ th.current.reverse { background-image: url(../images/control_arrow_up.gif); background-repeat: no-repeat; background-position: 60% 2px; -} \ No newline at end of file +} diff --git a/plugins/Directory/lib/sortablesubscriptionlist.php b/plugins/Directory/lib/sortablesubscriptionlist.php index 75c42a5fdf..d6df6c64cd 100644 --- a/plugins/Directory/lib/sortablesubscriptionlist.php +++ b/plugins/Directory/lib/sortablesubscriptionlist.php @@ -27,11 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/subscriptionlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Widget to show a sortable list of subscriptions @@ -128,32 +124,9 @@ class SortableSubscriptionList extends SubscriptionList $this->out->elementEnd('table'); } - function showProfiles() + function newListItem($profile) { - // Note: we don't use fetchAll() because it's borked with query() - - $profiles = array(); - - while ($this->profile->fetch()) { - $profiles[] = clone($this->profile); - } - - $cnt = count($profiles); - - $max = min($cnt, $this->maxProfiles()); - - for ($i = 0; $i < $max; $i++) { - $odd = ($i % 2 == 0); // for zebra striping - $pli = $this->newListItem($profiles[$i], $odd); - $pli->show(); - } - - return $cnt; - } - - function newListItem($profile, $odd) - { - return new SortableSubscriptionListItem($profile, $this->owner, $this->action, $odd); + return new SortableSubscriptionListItem($profile, $this->owner, $this->action); } } @@ -162,11 +135,10 @@ class SortableSubscriptionListItem extends SubscriptionListItem /** Owner of this list */ var $owner = null; - function __construct($profile, $owner, $action, $alt) + function __construct($profile, $owner, $action) { parent::__construct($profile, $owner, $action); - $this->alt = $alt; // is this row alternate? $this->owner = $owner; } @@ -177,10 +149,6 @@ class SortableSubscriptionListItem extends SubscriptionListItem 'id' => 'profile-' . $this->profile->id ); - if ($this->alt) { - $attr['class'] .= ' alt'; - } - $this->out->elementStart('tr', $attr); } diff --git a/theme/base/css/display.css b/theme/base/css/display.css index ddc225fb82..20f9aa775f 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -1622,6 +1622,11 @@ ul.profile_list li { display: block; } +table.profile_list tbody tr:nth-child(2n+1) { + background-color: #fafafa !important; + border: none !important; +} + .entity_profile .entity_nickname, .entity_profile .entity_fn { margin-left:0; diff --git a/theme/neo-gnu/css/display.css b/theme/neo-gnu/css/display.css index 39cce89e48..be9b80d41c 100644 --- a/theme/neo-gnu/css/display.css +++ b/theme/neo-gnu/css/display.css @@ -1141,11 +1141,6 @@ table.profile_list { background: url(../images/bluearrow_up.png) no-repeat top right; } -table.profile_list tr.alt { - background-color: #fafafa !important; - border: none !important; -} - td.entity_profile { width: auto; min-width: 250px; diff --git a/theme/neo/css/display.css b/theme/neo/css/display.css index 601845164b..575e6b7386 100644 --- a/theme/neo/css/display.css +++ b/theme/neo/css/display.css @@ -938,11 +938,6 @@ table.profile_list { background: url(../images/bluearrow_up.png) no-repeat top right; } -table.profile_list tr.alt { - background-color: #fafafa !important; - border: none !important; -} - td.entity_profile { width: auto; min-width: 250px; From d63bca9d3cf7f40442bb244e52b8a44166e736b8 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 14:22:22 +0200 Subject: [PATCH 021/156] Rss10Action now in an autodetected file. --- actions/allrss.php | 6 +----- actions/grouprss.php | 6 +----- actions/noticesearchrss.php | 6 +----- actions/publicrss.php | 6 +----- actions/repliesrss.php | 4 +--- actions/tagrss.php | 4 +--- actions/userrss.php | 2 -- lib/{rssaction.php => rss10action.php} | 0 plugins/Bookmark/actions/bookmarksrss.php | 6 +----- plugins/Favorite/actions/favoritesrss.php | 6 +----- 10 files changed, 8 insertions(+), 38 deletions(-) rename lib/{rssaction.php => rss10action.php} (100%) diff --git a/actions/allrss.php b/actions/allrss.php index fee52c79ab..d311701a57 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -28,11 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * RSS feed for user and friends timeline. diff --git a/actions/grouprss.php b/actions/grouprss.php index 87e34d73f8..dca323ad73 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -28,11 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } define('MEMBERS_PER_SECTION', 27); diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php index 14c280f62c..2ecdd547fb 100644 --- a/actions/noticesearchrss.php +++ b/actions/noticesearchrss.php @@ -28,11 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * RSS feed for notice search action class. diff --git a/actions/publicrss.php b/actions/publicrss.php index 11db3b37a4..7ad665e282 100644 --- a/actions/publicrss.php +++ b/actions/publicrss.php @@ -28,11 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * RSS feed for public timeline. diff --git a/actions/repliesrss.php b/actions/repliesrss.php index 145b51aaea..4e9af1e9ff 100644 --- a/actions/repliesrss.php +++ b/actions/repliesrss.php @@ -17,9 +17,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/rssaction.php'); +if (!defined('GNUSOCIAL')) { exit(1); } // Formatting of RSS handled by Rss10Action diff --git a/actions/tagrss.php b/actions/tagrss.php index 8a1494f131..66eb77c271 100644 --- a/actions/tagrss.php +++ b/actions/tagrss.php @@ -17,9 +17,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -require_once(INSTALLDIR.'/lib/rssaction.php'); +if (!defined('GNUSOCIAL')) { exit(1); } // Formatting of RSS handled by Rss10Action class TagrssAction extends Rss10Action diff --git a/actions/userrss.php b/actions/userrss.php index fe4ae155a3..aae41ee504 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -19,8 +19,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } -require_once(INSTALLDIR.'/lib/rssaction.php'); - // Formatting of RSS handled by Rss10Action class UserrssAction extends Rss10Action diff --git a/lib/rssaction.php b/lib/rss10action.php similarity index 100% rename from lib/rssaction.php rename to lib/rss10action.php diff --git a/plugins/Bookmark/actions/bookmarksrss.php b/plugins/Bookmark/actions/bookmarksrss.php index fc3331482f..1f2baa17d4 100644 --- a/plugins/Bookmark/actions/bookmarksrss.php +++ b/plugins/Bookmark/actions/bookmarksrss.php @@ -28,11 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * RSS feed for user bookmarks action class. diff --git a/plugins/Favorite/actions/favoritesrss.php b/plugins/Favorite/actions/favoritesrss.php index 0cbebd723c..b532e0bf31 100644 --- a/plugins/Favorite/actions/favoritesrss.php +++ b/plugins/Favorite/actions/favoritesrss.php @@ -28,11 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/rssaction.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * RSS feed for user favorites action class. From 9fb3ea3ce610b219d15c87f8606264b2797eea2f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 22:16:14 +0200 Subject: [PATCH 022/156] static function definitions and minor simplifying --- classes/Notice.php | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 8c9957958b..5fefa6c4ef 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1203,17 +1203,15 @@ class Notice extends Managed_DataObject $this->_attachments[$this->id] = $attachments; } - function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0) + static function publicStream($offset=0, $limit=20, $since_id=null, $max_id=null) { $stream = new PublicNoticeStream(); return $stream->getNotices($offset, $limit, $since_id, $max_id); } - - function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0) + static function conversationStream($id, $offset=0, $limit=20, $since_id=null, $max_id=null) { $stream = new ConversationNoticeStream($id); - return $stream->getNotices($offset, $limit, $since_id, $max_id); } @@ -1225,18 +1223,17 @@ class Notice extends Managed_DataObject */ function hasConversation() { - if (!empty($this->conversation)) { - $conversation = Notice::conversationStream( - $this->conversation, - 1, - 1 - ); - - if ($conversation->N > 0) { - return true; - } + if (empty($this->conversation)) { + // this notice is not part of a conversation apparently + // FIXME: all notices should have a conversation value, right? + return false; } - return false; + + $stream = new ConversationNoticeStream($this->conversation); + $notices = $stream->getNotices(/*offset*/ 1, /*limit*/ 1); + + // if our "offset 1, limit 1" query got a result, return true else false + return $notice->N > 0; } /** @@ -1271,8 +1268,7 @@ class Notice extends Managed_DataObject $root = new Notice; $root->conversation = $this->conversation; $root->orderBy('notice.created ASC'); - $root->find(); - $root->fetch(); + $root->find(true); // true means "fetch first result" $root->free(); return $root; } @@ -2193,7 +2189,7 @@ class Notice extends Managed_DataObject return $notice->fetchAll('id'); } - function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null) + static function locationOptions($lat, $lon, $location_id, $location_ns, $profile = null) { $options = array(); From 90565cc44d25d9fbf60629bf9092018c0634fa36 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 22:19:19 +0200 Subject: [PATCH 023/156] searchsubmenu item() function definition --- plugins/SearchSub/lib/searchsubmenu.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SearchSub/lib/searchsubmenu.php b/plugins/SearchSub/lib/searchsubmenu.php index fa5b34942b..434ed8982c 100644 --- a/plugins/SearchSub/lib/searchsubmenu.php +++ b/plugins/SearchSub/lib/searchsubmenu.php @@ -86,7 +86,7 @@ class SearchSubMenu extends MoreMenu return $items; } - function item($actionName, $args, $label, $description, $id=null, $cls=null) + function item($actionName, array $args, $label, $description, $id=null, $cls=null) { if (empty($id)) { $id = $this->menuItemID($actionName, $args); From ed248f7f5ab75f4e8a27a772ac975c9e5664851b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 22:26:03 +0200 Subject: [PATCH 024/156] No static calls (PEAR sucks ass) --- lib/mail.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mail.php b/lib/mail.php index 188792d02a..7ac743bfee 100644 --- a/lib/mail.php +++ b/lib/mail.php @@ -47,11 +47,13 @@ require_once 'Mail.php'; function mail_backend() { static $backend = null; + global $_PEAR; if (!$backend) { - $backend = Mail::factory(common_config('mail', 'backend'), + $mail = new Mail(); + $backend = $mail->factory(common_config('mail', 'backend'), common_config('mail', 'params') ?: array()); - if (PEAR::isError($backend)) { + if ($_PEAR->isError($backend)) { common_server_error($backend->getMessage(), 500); } } From 37e8b027f9f4e4922048e6ac9826291a4f9f4f07 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 22:29:23 +0200 Subject: [PATCH 025/156] PEAR's Mail_mimeDecode updated --- extlib/Mail/mimeDecode.php | 246 ++++++++++++++++++++++++++++++------- 1 file changed, 200 insertions(+), 46 deletions(-) diff --git a/extlib/Mail/mimeDecode.php b/extlib/Mail/mimeDecode.php index aaa870c509..a0d06942f4 100644 --- a/extlib/Mail/mimeDecode.php +++ b/extlib/Mail/mimeDecode.php @@ -52,7 +52,7 @@ * @author Sean Coates * @copyright 2003-2006 PEAR * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version CVS: $Id: mimeDecode.php,v 1.48 2006/12/03 13:43:33 cipri Exp $ + * @version CVS: $Id: mimeDecode.php 305875 2010-12-01 07:17:10Z alan_k $ * @link http://pear.php.net/package/Mail_mime */ @@ -147,6 +147,15 @@ class Mail_mimeDecode extends PEAR */ var $_decode_headers; + /** + * Flag to determine whether to include attached messages + * as body in the returned object. Depends on $_include_bodies + * + * @var boolean + * @access private + */ + var $_rfc822_bodies; + /** * Constructor. * @@ -165,6 +174,7 @@ class Mail_mimeDecode extends PEAR $this->_body = $body; $this->_decode_bodies = false; $this->_include_bodies = true; + $this->_rfc822_bodies = false; } /** @@ -187,7 +197,7 @@ class Mail_mimeDecode extends PEAR function decode($params = null) { // determine if this method has been called statically - $isStatic = !(isset($this) && get_class($this) == __CLASS__); + $isStatic = empty($this) || !is_a($this, __CLASS__); // Have we been called statically? // If so, create an object and pass details to that. @@ -208,6 +218,8 @@ class Mail_mimeDecode extends PEAR $params['decode_bodies'] : false; $this->_decode_headers = isset($params['decode_headers']) ? $params['decode_headers'] : false; + $this->_rfc822_bodies = isset($params['rfc_822bodies']) ? + $params['rfc_822bodies'] : false; $structure = $this->_decode($this->_header, $this->_body); if ($structure === false) { @@ -235,6 +247,7 @@ class Mail_mimeDecode extends PEAR $headers = $this->_parseHeaders($headers); foreach ($headers as $value) { + $value['value'] = $this->_decode_headers ? $this->_decodeHeader($value['value']) : $value['value']; if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) { $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]); $return->headers[strtolower($value['name'])][] = $value['value']; @@ -247,8 +260,8 @@ class Mail_mimeDecode extends PEAR } } - reset($headers); - while (list($key, $value) = each($headers)) { + + foreach ($headers as $key => $value) { $headers[$key]['name'] = strtolower($headers[$key]['name']); switch ($headers[$key]['name']) { @@ -261,7 +274,7 @@ class Mail_mimeDecode extends PEAR } if (isset($content_type['other'])) { - while (list($p_name, $p_value) = each($content_type['other'])) { + foreach($content_type['other'] as $p_name => $p_value) { $return->ctype_parameters[$p_name] = $p_value; } } @@ -271,7 +284,7 @@ class Mail_mimeDecode extends PEAR $content_disposition = $this->_parseHeaderValue($headers[$key]['value']); $return->disposition = $content_disposition['value']; if (isset($content_disposition['other'])) { - while (list($p_name, $p_value) = each($content_disposition['other'])) { + foreach($content_disposition['other'] as $p_name => $p_value) { $return->d_parameters[$p_name] = $p_value; } } @@ -303,6 +316,7 @@ class Mail_mimeDecode extends PEAR case 'multipart/alternative': case 'multipart/related': case 'multipart/mixed': + case 'application/vnd.wap.multipart.related': if(!isset($content_type['other']['boundary'])){ $this->_error = 'No boundary found for ' . $content_type['value'] . ' part'; return false; @@ -321,7 +335,11 @@ class Mail_mimeDecode extends PEAR break; case 'message/rfc822': - $obj = &new Mail_mimeDecode($body); + if ($this->_rfc822_bodies) { + $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit'; + $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body); + } + $obj = new Mail_mimeDecode($body); $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies, 'decode_bodies' => $this->_decode_bodies, 'decode_headers' => $this->_decode_headers)); @@ -401,6 +419,11 @@ class Mail_mimeDecode extends PEAR if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) { return array($match[1], $match[2]); } + // bug #17325 - empty bodies are allowed. - we just check that at least one line + // of headers exist.. + if (count(explode("\n",$input))) { + return array($input, ''); + } $this->_error = 'Could not split header and body'; return false; } @@ -419,7 +442,12 @@ class Mail_mimeDecode extends PEAR if ($input !== '') { // Unfold the input $input = preg_replace("/\r?\n/", "\r\n", $input); + //#7065 - wrapping.. with encoded stuff.. - probably not needed, + // wrapping space should only get removed if the trailing item on previous line is a + // encoded character + $input = preg_replace("/=\r\n(\t| )+/", '=', $input); $input = preg_replace("/\r\n(\t| )+/", ' ', $input); + $headers = explode("\r\n", trim($input)); foreach ($headers as $value) { @@ -430,7 +458,7 @@ class Mail_mimeDecode extends PEAR $return[] = array( 'name' => $hdr_name, - 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value + 'value' => $hdr_value ); } } else { @@ -454,41 +482,161 @@ class Mail_mimeDecode extends PEAR function _parseHeaderValue($input) { - if (($pos = strpos($input, ';')) !== false) { - - $return['value'] = trim(substr($input, 0, $pos)); - $input = trim(substr($input, $pos+1)); - - if (strlen($input) > 0) { - - // This splits on a semi-colon, if there's no preceeding backslash - // Now works with quoted values; had to glue the \; breaks in PHP - // the regex is already bordering on incomprehensible - $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/'; - preg_match_all($splitRegex, $input, $matches); - $parameters = array(); - for ($i=0; $i_decode_headers ? $this->_decodeHeader($input) : $input; $return['value'] = trim($input); + return $return; } + + + $value = substr($input, 0, $pos); + $value = $this->_decode_headers ? $this->_decodeHeader($value) : $value; + $return['value'] = trim($value); + $input = trim(substr($input, $pos+1)); + + if (!strlen($input) > 0) { + return $return; + } + // at this point input contains xxxx=".....";zzzz="...." + // since we are dealing with quoted strings, we need to handle this properly.. + $i = 0; + $l = strlen($input); + $key = ''; + $val = false; // our string - including quotes.. + $q = false; // in quote.. + $lq = ''; // last quote.. + + while ($i < $l) { + + $c = $input[$i]; + //var_dump(array('i'=>$i,'c'=>$c,'q'=>$q, 'lq'=>$lq, 'key'=>$key, 'val' =>$val)); + + $escaped = false; + if ($c == '\\') { + $i++; + if ($i == $l-1) { // end of string. + break; + } + $escaped = true; + $c = $input[$i]; + } + + + // state - in key.. + if ($val === false) { + if (!$escaped && $c == '=') { + $val = ''; + $key = trim($key); + $i++; + continue; + } + if (!$escaped && $c == ';') { + if ($key) { // a key without a value.. + $key= trim($key); + $return['other'][$key] = ''; + $return['other'][strtolower($key)] = ''; + } + $key = ''; + } + $key .= $c; + $i++; + continue; + } + + // state - in value.. (as $val is set..) + + if ($q === false) { + // not in quote yet. + if ((!strlen($val) || $lq !== false) && $c == ' ' || $c == "\t") { + $i++; + continue; // skip leading spaces after '=' or after '"' + } + if (!$escaped && ($c == '"' || $c == "'")) { + // start quoted area.. + $q = $c; + // in theory should not happen raw text in value part.. + // but we will handle it as a merged part of the string.. + $val = !strlen(trim($val)) ? '' : trim($val); + $i++; + continue; + } + // got end.... + if (!$escaped && $c == ';') { + + $val = trim($val); + $added = false; + if (preg_match('/\*[0-9]+$/', $key)) { + // this is the extended aaa*0=...;aaa*1=.... code + // it assumes the pieces arrive in order, and are valid... + $key = preg_replace('/\*[0-9]+$/', '', $key); + if (isset($return['other'][$key])) { + $return['other'][$key] .= $val; + if (strtolower($key) != $key) { + $return['other'][strtolower($key)] .= $val; + } + $added = true; + } + // continue and use standard setters.. + } + if (!$added) { + $return['other'][$key] = $val; + $return['other'][strtolower($key)] = $val; + } + $val = false; + $key = ''; + $lq = false; + $i++; + continue; + } + + $val .= $c; + $i++; + continue; + } + + // state - in quote.. + if (!$escaped && $c == $q) { // potential exit state.. + + // end of quoted string.. + $lq = $q; + $q = false; + $i++; + continue; + } + + // normal char inside of quoted string.. + $val.= $c; + $i++; + } + + // do we have anything left.. + if (strlen(trim($key)) || $val !== false) { + + $val = trim($val); + $added = false; + if ($val !== false && preg_match('/\*[0-9]+$/', $key)) { + // no dupes due to our crazy regexp. + $key = preg_replace('/\*[0-9]+$/', '', $key); + if (isset($return['other'][$key])) { + $return['other'][$key] .= $val; + if (strtolower($key) != $key) { + $return['other'][strtolower($key)] .= $val; + } + $added = true; + } + // continue and use standard setters.. + } + if (!$added) { + $return['other'][$key] = $val; + $return['other'][strtolower($key)] = $val; + } + } + // decode values. + foreach($return['other'] as $key =>$val) { + $return['other'][$key] = $this->_decode_headers ? $this->_decodeHeader($val) : $val; + } + //print_r($return); return $return; } @@ -510,13 +658,19 @@ class Mail_mimeDecode extends PEAR if ($boundary == $bs_check) { $boundary = $bs_possible; } + $tmp = preg_split("/--".preg_quote($boundary, '/')."((?=\s)|--)/", $input); - $tmp = explode('--' . $boundary, $input); - - for ($i = 1; $i < count($tmp) - 1; $i++) { - $parts[] = $tmp[$i]; + $len = count($tmp) -1; + for ($i = 1; $i < $len; $i++) { + if (strlen(trim($tmp[$i]))) { + $parts[] = $tmp[$i]; + } + } + + // add the last part on if it does not end with the 'closing indicator' + if (!empty($tmp[$len]) && strlen(trim($tmp[$len])) && $tmp[$len][0] != '-') { + $parts[] = $tmp[$len]; } - return $parts; } @@ -719,7 +873,7 @@ class Mail_mimeDecode extends PEAR case "to": case "cc": case "bcc": - $to = ",".$item['value']; + $to .= ",".$item['value']; default: break; } From 06f60b57c10c37fe6114ff09949b187e4cb2c964 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 23:01:50 +0200 Subject: [PATCH 026/156] StartpageAction essentially duplicated TopAction --- actions/logout.php | 2 +- actions/startpage.php | 26 ----------------- actions/top.php | 66 +++++++++---------------------------------- lib/router.php | 13 ++++----- 4 files changed, 21 insertions(+), 86 deletions(-) delete mode 100644 actions/startpage.php diff --git a/actions/logout.php b/actions/logout.php index 60aec3c83a..5394a38c9f 100644 --- a/actions/logout.php +++ b/actions/logout.php @@ -63,7 +63,7 @@ class LogoutAction extends ManagedAction } Event::handle('EndLogout', array($this)); - common_redirect(common_local_url('startpage')); + common_redirect(common_local_url('top')); } // Accessed through the action on events diff --git a/actions/startpage.php b/actions/startpage.php deleted file mode 100644 index 9ce00d9c53..0000000000 --- a/actions/startpage.php +++ /dev/null @@ -1,26 +0,0 @@ - $user->nickname)), 303); - } elseif (common_config('public', 'localonly')) { - common_redirect(common_local_url('public'), 303); - } else { - common_redirect(common_local_url('networkpublic'), 303); - } - } -} diff --git a/actions/top.php b/actions/top.php index 39abe3df26..d30a40bafe 100644 --- a/actions/top.php +++ b/actions/top.php @@ -20,67 +20,29 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Top - * @package StatusNet - * @author Evan Prodromou - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} - -/** - * An action to redirect to the top of the site - * * @category Action - * @package StatusNet + * @package GNUsocial * @author Evan Prodromou + * @author Mikael Nordfeldth * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ + * @copyright 2015 Free Software Foundation, Inc. + * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL 3.0 + * @link https://gnu.io/social */ -class TopAction extends Action +if (!defined('GNUSOCIAL')) { exit(1); } + +class TopAction extends ManagedAction { - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - - function prepare($argarray) - { - parent::prepare($argarray); - return true; - } - - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - - function handle($argarray=null) + public function showPage() { if (common_config('singleuser', 'enabled')) { - $url = common_local_url('showstream', array('nickname' => User::singleUserNickname())); + $user = User::singleUser(); + common_redirect(common_local_url('showstream', array('nickname' => $user->getNickname())), 303); + } elseif (common_config('public', 'localonly')) { + common_redirect(common_local_url('public'), 303); } else { - $url = common_local_url('public'); + common_redirect(common_local_url('networkpublic'), 303); } - - // XXX: Permanent? I think so. - - common_redirect($url, 301); - - return; } } diff --git a/lib/router.php b/lib/router.php index ca8daf5a90..f6360377da 100644 --- a/lib/router.php +++ b/lib/router.php @@ -108,6 +108,11 @@ class Router if (Event::handle('StartInitializeRouter', array(&$m))) { + // top of the menu hierarchy, sometimes "Home" + $m->connect('', array('action' => 'top')); + + // public endpoints + $m->connect('robots.txt', array('action' => 'robotstxt')); $m->connect('opensearch/people', array('action' => 'opensearch', @@ -156,13 +161,13 @@ class Router 'deleteaccount', 'restoreaccount', 'top', + 'public', ); foreach ($main as $a) { $m->connect('main/'.$a, array('action' => $a)); } - $m->connect('main/public', array('action' => 'public')); $m->connect('main/all', array('action' => 'networkpublic')); $m->connect('main/tagprofile/:id', array('action' => 'tagprofile'), @@ -875,9 +880,6 @@ class Router array('action' => 'rsd', 'nickname' => $nickname)); - $m->connect('', - array('action' => 'startpage')); - // peopletags $m->connect('peopletags', @@ -930,9 +932,6 @@ class Router } } - $m->connect('', array('action' => 'startpage')); - $m->connect('main/public', array('action' => 'public')); - $m->connect('main/all', array('action' => 'networkpublic')); $m->connect('rss', array('action' => 'publicrss')); $m->connect('featuredrss', array('action' => 'featuredrss')); $m->connect('featured/', array('action' => 'featured')); From b5b7a27f9b5801cbc42ac8b0094fd89304e3e659 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 9 Jul 2015 23:14:23 +0200 Subject: [PATCH 027/156] Rss10Action migrated to ManagedAction --- lib/action.php | 2 +- lib/rss10action.php | 63 ++++++--------------------------------------- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/lib/action.php b/lib/action.php index 29fadbf883..7f3aea9c1a 100644 --- a/lib/action.php +++ b/lib/action.php @@ -205,7 +205,7 @@ class Action extends HTMLOutputter // lawsuit * * @return nothing */ - function showPage() + public function showPage() { if (GNUsocial::isAjax()) { self::showAjax(); diff --git a/lib/rss10action.php b/lib/rss10action.php index bb4fa12b74..8d73bec1d9 100644 --- a/lib/rss10action.php +++ b/lib/rss10action.php @@ -28,11 +28,11 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } define('DEFAULT_RSS_LIMIT', 48); -class Rss10Action extends Action +class Rss10Action extends ManagedAction { // This will contain the details of each feed item's author and be used to generate SIOC data. @@ -41,45 +41,14 @@ class Rss10Action extends Action var $notices = null; var $tags_already_output = array(); - /** - * Constructor - * - * Just wraps the Action constructor. - * - * @param string $output URI to output to, default = stdout - * @param boolean $indent Whether to indent output, default true - * - * @see Action::__construct - */ - - function __construct($output='php://output', $indent=null) - { - parent::__construct($output, $indent); - } - - /** - * Do we need to write to the database? - * - * @return boolean true - */ - - function isReadonly() + public function isReadOnly($args) { return true; } - /** - * Read arguments and initialize members - * - * @param array $args Arguments from $_REQUEST - * @return boolean success - */ - - protected function prepare(array $args=array()) + protected function doPreparation() { - parent::prepare($args); - - $this->limit = (int) $this->trimmed('limit'); + $this->limit = $this->int('limit'); if ($this->limit == 0) { $this->limit = DEFAULT_RSS_LIMIT; @@ -93,7 +62,7 @@ class Rss10Action extends Action // If the user hits cancel -- bam! $this->show_basic_auth_error(); - return; + // the above calls 'exit' } else { $nickname = $_SERVER['PHP_AUTH_USER']; $password = $_SERVER['PHP_AUTH_PW']; @@ -108,23 +77,6 @@ class Rss10Action extends Action } } } - - return true; - } - - /** - * Handle a request - * - * @param array $args Arguments from $_REQUEST - * - * @return void - */ - - protected function handle() - { - // Parent handling, including cache check - parent::handle(); - $this->showRss(); } function show_basic_auth_error() @@ -137,6 +89,7 @@ class Rss10Action extends Action $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); $this->endXML(); + exit; } /** @@ -170,7 +123,7 @@ class Rss10Action extends Action return null; } - function showRss() + function showPage() { $this->initRss(); $this->showChannel(); From 8cc85f684b7615009f4e29963a2d32b08293dee2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 00:08:09 +0200 Subject: [PATCH 028/156] Need to supply data to NoSuchUserException --- classes/User.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/classes/User.php b/classes/User.php index 3efaa5e721..9ae0cba804 100644 --- a/classes/User.php +++ b/classes/User.php @@ -142,6 +142,16 @@ class User extends Managed_DataObject return $this->getProfile()->getNickname(); } + public function getByNickname($nickname) + { + $user = User::getKV('nickname', $nickname); + if (!$user instanceof User) { + throw new NoSuchUserException(array('nickname' => $nickname)); + } + + return $user; + } + function isSubscribed(Profile $other) { return $this->getProfile()->isSubscribed($other); @@ -439,7 +449,7 @@ class User extends Managed_DataObject function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { - return Reply::stream($this->id, $offset, $limit, $since_id, $before_id); + return $this->getProfile()->getReplies($offset, $limit, $since_id, $before_id); } function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) { From 1cbf2510e7326086a16106eb6566bcfaa330179c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 00:27:26 +0200 Subject: [PATCH 029/156] /:nickname/all/rss had to be before /:tagger/all/:tag ...though this makes it impossible to get a list called "rss" so FIXME, the RSS 1.0 actions should be in the API or something --- lib/router.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/router.php b/lib/router.php index f6360377da..249e5e8823 100644 --- a/lib/router.php +++ b/lib/router.php @@ -949,6 +949,13 @@ class Router array('action' => 'subqueue'), array('nickname' => Nickname::DISPLAY_FMT)); + // some targeted RSS 1.0 actions (extends TargetedRss10Action) + foreach (array('all', 'replies') as $a) { + $m->connect(':nickname/'.$a.'/rss', + array('action' => $a.'rss'), + array('nickname' => Nickname::DISPLAY_FMT)); + } + // people tags $m->connect(':nickname/peopletags', @@ -1016,12 +1023,6 @@ class Router array('nickname' => Nickname::DISPLAY_FMT)); } - foreach (array('all', 'replies') as $a) { - $m->connect(':nickname/'.$a.'/rss', - array('action' => $a.'rss'), - array('nickname' => Nickname::DISPLAY_FMT)); - } - $m->connect(':nickname/avatar', array('action' => 'avatarbynickname'), array('nickname' => Nickname::DISPLAY_FMT)); From e46b2803a728104adcd17b19912f8f7a9a6428fd Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 00:28:36 +0200 Subject: [PATCH 030/156] Introducing TargetedRss10Action for simplifying RSS 1.0 --- actions/allrss.php | 72 +++----------------- actions/grouprss.php | 56 ++++------------ actions/noticesearchrss.php | 17 +---- actions/publicrss.php | 34 +--------- actions/repliesrss.php | 50 +++----------- actions/tagrss.php | 24 +++---- actions/userrss.php | 82 +++++------------------ classes/Local_group.php | 2 +- classes/Profile.php | 5 ++ classes/Reply.php | 3 +- lib/rss10action.php | 15 ++++- plugins/Bookmark/actions/bookmarksrss.php | 70 +++---------------- plugins/Favorite/actions/favoritesrss.php | 58 ++-------------- 13 files changed, 93 insertions(+), 395 deletions(-) diff --git a/actions/allrss.php b/actions/allrss.php index d311701a57..4b6df25048 100644 --- a/actions/allrss.php +++ b/actions/allrss.php @@ -42,52 +42,12 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ */ -class AllrssAction extends Rss10Action +class AllrssAction extends TargetedRss10Action { - var $user = null; - - /** - * Initialization. - * - * @param array $args Web and URL arguments - * - * @return boolean false if user doesn't exist - * - */ - function prepare($args) + protected function getNotices() { - parent::prepare($args); - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error when user not found for an rss related action. - $this->clientError(_('No such user.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; - } - } - - /** - * Get notices - * - * @param integer $limit max number of notices to return - * - * @return array notices - */ - function getNotices($limit=0) - { - $stream = new InboxNoticeStream($this->user->getProfile()); - $notice = $stream->getNotices(0, $limit, null, null); - - $notices = array(); - - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; + $stream = new InboxNoticeStream($this->target); + return $stream->getNotices(0, $this->limit)->fetchAll(); } /** @@ -97,33 +57,17 @@ class AllrssAction extends Rss10Action */ function getChannel() { - $user = $this->user; $c = array('url' => common_local_url('allrss', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: Message is used as link title. %s is a user nickname. - 'title' => sprintf(_('%s and friends'), $user->nickname), + 'title' => sprintf(_('%s and friends'), $this->target->getNickname()), 'link' => common_local_url('all', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: Message is used as link description. %1$s is a username, %2$s is a site name. 'description' => sprintf(_('Updates from %1$s and friends on %2$s!'), - $user->nickname, common_config('site', 'name'))); + $this->target->getNickname(), common_config('site', 'name'))); return $c; } - - /** - * Get image. - * - * @return string user avatar URL or null - */ - function getImage() - { - $user = $this->user; - $profile = $user->getProfile(); - if (!$profile) { - return null; - } - return $profile->avatarUrl(AVATAR_PROFILE_SIZE); - } } diff --git a/actions/grouprss.php b/actions/grouprss.php index dca323ad73..14d85d89cd 100644 --- a/actions/grouprss.php +++ b/actions/grouprss.php @@ -41,10 +41,10 @@ define('MEMBERS_PER_SECTION', 27); * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class groupRssAction extends Rss10Action +class GroupRssAction extends TargetedRss10Action { /** group we're viewing. */ - var $group = null; + protected $group = null; /** * Is this page read-only? @@ -56,18 +56,8 @@ class groupRssAction extends Rss10Action return true; } - /** - * Prepare the action - * - * Reads and validates arguments and instantiates the attributes. - * - * @param array $args $_REQUEST args - * - * @return boolean success flag - */ - function prepare($args) + protected function doStreamPreparation() { - parent::prepare($args); $nickname_arg = $this->arg('nickname'); $nickname = common_canonical_nickname($nickname_arg); @@ -86,52 +76,32 @@ class groupRssAction extends Rss10Action $local = Local_group::getKV('nickname', $nickname); - if (!$local) { + if (!$local instanceof Local_group) { // TRANS: Client error displayed when requesting a group RSS feed for group that does not exist. $this->clientError(_('No such group.'), 404); } - $this->group = User_group::getKV('id', $local->group_id); - - if (!$this->group) { - // TRANS: Client error displayed when requesting a group RSS feed for an object that is not a group. - $this->clientError(_('No such group.'), 404); - } - - $this->notices = $this->getNotices($this->limit); - return true; + $this->group = $local->getGroup(); + $this->target = $this->group->getProfile(); } - function getNotices($limit=0) + protected function getNotices() { - $group = $this->group; - - if (is_null($group)) { - return null; - } - - $notices = array(); - $notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit); - - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; + $stream = $this->group->getNotices(0, $this->limit); + return $stream->fetchAll(); } function getChannel() { - $group = $this->group; $c = array('url' => common_local_url('grouprss', array('nickname' => - $group->nickname)), + $this->target->getNickname())), // TRANS: Message is used as link title. %s is a user nickname. - 'title' => sprintf(_('%s timeline'), $group->nickname), - 'link' => common_local_url('showgroup', array('nickname' => $group->nickname)), + 'title' => sprintf(_('%s timeline'), $this->target->getNickname()), + 'link' => common_local_url('showgroup', array('nickname' => $this->target->getNickname())), // TRANS: Message is used as link description. %1$s is a group name, %2$s is a site name. 'description' => sprintf(_('Updates from members of %1$s on %2$s!'), - $group->nickname, common_config('site', 'name'))); + $this->target->getNickname(), common_config('site', 'name'))); return $c; } diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php index 2ecdd547fb..2a5187b885 100644 --- a/actions/noticesearchrss.php +++ b/actions/noticesearchrss.php @@ -44,19 +44,7 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class NoticesearchrssAction extends Rss10Action { - function init() - { - return true; - } - - function prepare($args) - { - parent::prepare($args); - $this->notices = $this->getNotices(); - return true; - } - - function getNotices($limit=0) + protected function getNotices() { $q = $this->trimmed('q'); $notices = array(); @@ -66,8 +54,7 @@ class NoticesearchrssAction extends Rss10Action $search_engine = $notice->getSearchEngine('notice'); $search_engine->set_sort_mode('chron'); - if (!$limit) $limit = 20; - $search_engine->limit(0, $limit, true); + $search_engine->limit(0, $this->limit, true); if (false === $search_engine->query($q)) { $cnt = 0; } else { diff --git a/actions/publicrss.php b/actions/publicrss.php index 7ad665e282..5dcff3ba3d 100644 --- a/actions/publicrss.php +++ b/actions/publicrss.php @@ -44,29 +44,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class PublicrssAction extends Rss10Action { - /** - * Read arguments and initialize members - * - * @param array $args Arguments from $_REQUEST - * @return boolean success - */ - function prepare($args) - { - parent::prepare($args); - $this->notices = $this->getNotices($this->limit); - return true; - } - - /** - * Initialization. - * - * @return boolean true - */ - function init() - { - return true; - } - /** * Get notices * @@ -74,15 +51,10 @@ class PublicrssAction extends Rss10Action * * @return array notices */ - function getNotices($limit=0) + protected function getNotices() { - $notices = array(); - $notice = Notice::publicStream(0, ($limit == 0) ? 48 : $limit); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; + $stream = Notice::publicStream(0, $this->limit); + return $stream->fetchAll(); } /** diff --git a/actions/repliesrss.php b/actions/repliesrss.php index 4e9af1e9ff..54f83592c0 100644 --- a/actions/repliesrss.php +++ b/actions/repliesrss.php @@ -21,64 +21,30 @@ if (!defined('GNUSOCIAL')) { exit(1); } // Formatting of RSS handled by Rss10Action -class RepliesrssAction extends Rss10Action +class RepliesrssAction extends TargetedRss10Action { - var $user = null; - - function prepare($args) + protected function getNotices() { - parent::prepare($args); - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when providing a non-existing nickname in a RSS 1.0 action. - $this->clientError(_('No such user.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; - } - } - - function getNotices($limit=0) - { - $user = $this->user; - - $notice = $user->getReplies(0, ($limit == 0) ? 48 : $limit); - - $notices = array(); - - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; + $stream = $this->target->getReplies(0, $this->limit); + return $stream->fetchAll(); } function getChannel() { - $user = $this->user; $c = array('url' => common_local_url('repliesrss', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: RSS reply feed title. %s is a user nickname. - 'title' => sprintf(_("Replies to %s"), $user->nickname), + 'title' => sprintf(_("Replies to %s"), $this->target->getNickname()), 'link' => common_local_url('replies', - array('nickname' => - $user->nickname)), + array('nickname' => $this->target->getNickname())), // TRANS: RSS reply feed description. // TRANS: %1$s is a user nickname, %2$s is the StatusNet site name. 'description' => sprintf(_('Replies to %1$s on %2$s.'), - $user->nickname, common_config('site', 'name'))); + $this->target->getNickname(), common_config('site', 'name'))); return $c; } - function getImage() - { - $profile = $this->user->getProfile(); - return $profile->avatarUrl(AVATAR_PROFILE_SIZE); - } - function isReadOnly($args) { return true; diff --git a/actions/tagrss.php b/actions/tagrss.php index 66eb77c271..0d4d68ffba 100644 --- a/actions/tagrss.php +++ b/actions/tagrss.php @@ -20,33 +20,25 @@ if (!defined('GNUSOCIAL')) { exit(1); } // Formatting of RSS handled by Rss10Action + class TagrssAction extends Rss10Action { - var $tag; + protected $tag; - function prepare($args) { - parent::prepare($args); + protected function doStreamPreparation() + { $tag = common_canonical_tag($this->trimmed('tag')); $this->tag = Notice_tag::getKV('tag', $tag); - if (!$this->tag) { + if (!$this->tag instanceof Notice_tag) { // TRANS: Client error when requesting a tag feed for a non-existing tag. $this->clientError(_('No such tag.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; } } - function getNotices($limit=0) + protected function getNotices() { - $tag = $this->tag; - - if (is_null($tag)) { - return null; - } - - $notice = Notice_tag::getStream($tag->tag)->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit); - return $notice->fetchAll(); + $stream = Notice_tag::getStream($this->tag->tag)->getNotices(0, $this->limit); + return $stream->fetchAll(); } function getChannel() diff --git a/actions/userrss.php b/actions/userrss.php index aae41ee504..fd49a0e899 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -21,94 +21,48 @@ if (!defined('GNUSOCIAL')) { exit(1); } // Formatting of RSS handled by Rss10Action -class UserrssAction extends Rss10Action +class UserrssAction extends TargetedRss10Action { - var $tag = null; + protected $tag = null; - protected function prepare(array $args=array()) + protected function doStreamPreparation() { - parent::prepare($args); - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); $this->tag = $this->trimmed('tag'); + } - if (!$this->user) { - // TRANS: Client error displayed when user not found for an action. - $this->clientError(_('No such user.')); - } - + protected function getNotices() + { if (!empty($this->tag)) { - $this->notices = $this->getTaggedNotices(); - } else { - $this->notices = $this->getNotices(); + $stream = $this->target->getTaggedNotices($this->tag, 0, $this->limit); + return $stream->fetchAll(); } + // otherwise we fetch a normal user stream - return true; - } - - function getTaggedNotices() - { - $notice = $this->user->getTaggedNotices( - $this->tag, - 0, - ($this->limit == 0) ? NOTICES_PER_PAGE : $this->limit, - 0, - 0 - ); - - $notices = array(); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; - } - - - function getNotices() - { - $notice = $this->user->getNotices( - 0, - ($this->limit == 0) ? NOTICES_PER_PAGE : $this->limit - ); - - $notices = array(); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - - return $notices; + $stream = $this->target->getNotices(0, $this->limit); + return $stream->fetchAll(); } function getChannel() { - $user = $this->user; - $profile = $user->getProfile(); $c = array('url' => common_local_url('userrss', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: Message is used as link title. %s is a user nickname. - 'title' => sprintf(_('%s timeline'), $user->nickname), - 'link' => $profile->profileurl, + 'title' => sprintf(_('%s timeline'), $this->target->getNickname()), + 'link' => $this->target->getUrl(), // TRANS: Message is used as link description. %1$s is a username, %2$s is a site name. 'description' => sprintf(_('Updates from %1$s on %2$s!'), - $user->nickname, common_config('site', 'name'))); + $this->target->getNickname(), common_config('site', 'name'))); return $c; } - function getImage() - { - $profile = $this->user->getProfile(); - return $profile->avatarUrl(AVATAR_PROFILE_SIZE); - } - // override parent to add X-SUP-ID URL - function initRss($limit=0) + function initRss() { - $url = common_local_url('sup', null, null, $this->user->id); + $url = common_local_url('sup', null, null, $this->target->getID()); header('X-SUP-ID: '.$url); - parent::initRss($limit); + parent::initRss(); } function isReadOnly($args) diff --git a/classes/Local_group.php b/classes/Local_group.php index 9e95102d85..1cebd4c40c 100644 --- a/classes/Local_group.php +++ b/classes/Local_group.php @@ -50,7 +50,7 @@ class Local_group extends Managed_DataObject $group->find(true); if (!$group instanceof User_group) { common_log(LOG_ERR, 'User_group does not exist for Local_group: '.$this->group_id); - throw new NoResultException($group); + throw new NoSuchGroupException(array('id' => $this->group_id)); } return $group; } diff --git a/classes/Profile.php b/classes/Profile.php index b5ba00caa9..f78cd34e36 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -242,6 +242,11 @@ class Profile extends Managed_DataObject return null; } + function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) + { + return Reply::stream($this->getID(), $offset, $limit, $since_id, $before_id); + } + function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $stream = new TaggedProfileNoticeStream($this, $tag); diff --git a/classes/Reply.php b/classes/Reply.php index 36686d0c76..d3405e6581 100644 --- a/classes/Reply.php +++ b/classes/Reply.php @@ -55,10 +55,9 @@ class Reply extends Managed_DataObject return $result; } - function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) + static function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0) { $stream = new ReplyNoticeStream($user_id); - return $stream->getNotices($offset, $limit, $since_id, $max_id); } } diff --git a/lib/rss10action.php b/lib/rss10action.php index 8d73bec1d9..7b6eed9c96 100644 --- a/lib/rss10action.php +++ b/lib/rss10action.php @@ -50,7 +50,7 @@ class Rss10Action extends ManagedAction { $this->limit = $this->int('limit'); - if ($this->limit == 0) { + if (empty($this->limit)) { $this->limit = DEFAULT_RSS_LIMIT; } @@ -73,10 +73,19 @@ class Rss10Action extends ManagedAction common_log(LOG_WARNING, "Failed RSS auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip."); $this->show_basic_auth_error(); - return; + // the above calls 'exit' } } } + + $this->doStreamPreparation(); + + $this->notices = $this->getNotices($this->limit); + } + + protected function doStreamPreparation() + { + // for example if we need to set $this->target or something } function show_basic_auth_error() @@ -98,7 +107,7 @@ class Rss10Action extends ManagedAction * @return array an array of Notice objects sorted in reverse chron */ - function getNotices() + protected function getNotices($limit=0) { return array(); } diff --git a/plugins/Bookmark/actions/bookmarksrss.php b/plugins/Bookmark/actions/bookmarksrss.php index 1f2baa17d4..955b782666 100644 --- a/plugins/Bookmark/actions/bookmarksrss.php +++ b/plugins/Bookmark/actions/bookmarksrss.php @@ -44,54 +44,12 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ */ -class BookmarksrssAction extends Rss10Action +class BookmarksrssAction extends TargetedRss10Action { - /** The user whose bookmarks to display */ - - var $user = null; - - /** - * Find the user to display by supplied nickname - * - * @param array $args Arguments from $_REQUEST - * - * @return boolean success - */ - function prepare($args) - { - parent::prepare($args); - - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when trying to get the RSS feed with bookmarks of a user that does not exist. - $this->clientError(_('No such user.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; - } - } - - /** - * Get notices - * - * @param integer $limit max number of notices to return - * - * @return array notices - */ - function getNotices($limit=0) + protected function getNotices() { - $user = $this->user; - - $notice = new BookmarksNoticeStream($this->user->id, true); - $notice = $notice->getNotices(0, NOTICES_PER_PAGE); - - $notices = array(); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - return $notices; + $stream = new BookmarksNoticeStream($this->target->getID(), true); + return $stream->getNotices(0, $this->limit)->fetchAll(); } /** @@ -101,31 +59,19 @@ class BookmarksrssAction extends Rss10Action */ function getChannel() { - $user = $this->user; $c = array('url' => common_local_url('bookmarksrss', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: Title of RSS feed with bookmarks of a user. // TRANS: %s is a user's nickname. - 'title' => sprintf(_("%s's bookmarks"), $user->nickname), + 'title' => sprintf(_("%s's bookmarks"), $this->target->getNickname()), 'link' => common_local_url('bookmarks', array('nickname' => - $user->nickname)), + $this->target->getNickname())), // TRANS: Desciption of RSS feed with bookmarks of a user. // TRANS: %1$s is a user's nickname, %2$s is the name of the StatusNet site. 'description' => sprintf(_('Bookmarks posted by %1$s on %2$s!'), - $user->nickname, common_config('site', 'name'))); + $this->target->getNickname(), common_config('site', 'name'))); return $c; } - - /** - * Get image. - * - * @return void - */ - function getImage() - { - return null; - } - } diff --git a/plugins/Favorite/actions/favoritesrss.php b/plugins/Favorite/actions/favoritesrss.php index b532e0bf31..ca5602d5ec 100644 --- a/plugins/Favorite/actions/favoritesrss.php +++ b/plugins/Favorite/actions/favoritesrss.php @@ -43,50 +43,15 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ */ -class FavoritesrssAction extends Rss10Action +class FavoritesrssAction extends TargetedRss10Action { - /** The user whose favorites to display */ - - var $user = null; - - /** - * Find the user to display by supplied nickname - * - * @param array $args Arguments from $_REQUEST - * - * @return boolean success - */ - function prepare($args) + protected function getNotices() { - parent::prepare($args); + // is this our own stream? + $own = $this->scoped instanceof Profile ? $this->target->getID() === $this->scoped->getID() : false; - $nickname = $this->trimmed('nickname'); - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when trying to get the RSS feed with favorites of a user that does not exist. - $this->clientError(_('No such user.')); - } else { - $this->notices = $this->getNotices($this->limit); - return true; - } - } - - /** - * Get notices - * - * @param integer $limit max number of notices to return - * - * @return array notices - */ - function getNotices($limit=0) - { - $notice = Fave::stream($this->user->id, 0, $limit, $false); - $notices = array(); - while ($notice->fetch()) { - $notices[] = clone($notice); - } - return $notices; + $stream = Fave::stream($this->target->getID(), 0, $this->limit, $own); + return $stream->fetchAll(); } /** @@ -112,15 +77,4 @@ class FavoritesrssAction extends Rss10Action $user->nickname, common_config('site', 'name'))); return $c; } - - /** - * Get image. - * - * @return void - */ - function getImage() - { - return null; - } - } From 76432b958be92296431e0f813859ee458029feed Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 00:52:22 +0200 Subject: [PATCH 031/156] delete notice form fix --- actions/deletenotice.php | 143 ++++----------------------------------- 1 file changed, 14 insertions(+), 129 deletions(-) diff --git a/actions/deletenotice.php b/actions/deletenotice.php index eb84b4f3ae..8f0211f8f9 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -28,80 +28,26 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } // @todo FIXME: documentation needed. -class DeletenoticeAction extends Action +class DeletenoticeAction extends FormAction { - var $error = null; - var $user = null; - var $notice = null; - var $profile = null; - var $user_profile = null; + protected $form = 'deletenotice'; - function prepare($args) + protected $notice = null; + + protected function doPreparation() { - parent::prepare($args); + $this->notice = Notice::getByID($this->int('notice')); - $this->user = common_current_user(); - - if (!$this->user) { - // TRANS: Error message displayed when trying to perform an action that requires a logged in user. - common_user_error(_('Not logged in.')); - exit; - } - - $notice_id = $this->trimmed('notice'); - $this->notice = Notice::getKV($notice_id); - - if (!$this->notice) { - // TRANS: Error message displayed trying to delete a non-existing notice. - common_user_error(_('No such notice.')); - exit; - } - - $this->profile = $this->notice->getProfile(); - $this->user_profile = $this->user->getProfile(); - - return true; - } - - function handle($args) - { - parent::handle($args); - - if ($this->notice->profile_id != $this->user_profile->id && - !$this->user->hasRight(Right::DELETEOTHERSNOTICE)) { + if ($this->notice->profile_id != $this->scoped->getID() && + !$this->scoped->hasRight(Right::DELETEOTHERSNOTICE)) { // TRANS: Error message displayed trying to delete a notice that was not made by the current user. - common_user_error(_('Cannot delete this notice.')); - exit; + $this->clientError(_('Cannot delete this notice.')); } - // XXX: Ajax! - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->deleteNotice(); - } else if ($_SERVER['REQUEST_METHOD'] == 'GET') { - $this->showForm(); - } - } - - /** - * Show the page notice - * - * Shows instructions for the page - * - * @return void - */ - function showPageNotice() - { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - - $this->elementStart('div', 'instructions'); - $this->raw($output); - $this->elementEnd('div'); + $this->formOpts['notice'] = $this->notice; } function getInstructions() @@ -117,73 +63,12 @@ class DeletenoticeAction extends Action return _('Delete notice'); } - /** - * Wrapper for showing a page - * - * Stores an error and shows the page - * - * @param string $error Error, if any - * - * @return void - */ - function showForm($error = null) + protected function doPost() { - $this->error = $error; - $this->showPage(); - } - - /** - * Insert delete notice form into the content - * - * @return void - */ - function showContent() - { - $this->elementStart('form', array('id' => 'form_notice_delete', - 'class' => 'form_settings', - 'method' => 'post', - 'action' => common_local_url('deletenotice'))); - $this->elementStart('fieldset'); - // TRANS: Fieldset legend for the delete notice form. - $this->element('legend', null, _('Delete notice')); - $this->hidden('token', common_session_token()); - $this->hidden('notice', $this->trimmed('notice')); - // TRANS: Message for the delete notice form. - $this->element('p', null, _('Are you sure you want to delete this notice?')); - $this->submit('form_action-no', - // TRANS: Button label on the delete notice form. - _m('BUTTON','No'), - 'submit form_action-primary', - 'no', - // TRANS: Submit button title for 'No' when deleting a notice. - _('Do not delete this notice.')); - $this->submit('form_action-yes', - // TRANS: Button label on the delete notice form. - _m('BUTTON','Yes'), - 'submit form_action-secondary', - 'yes', - // TRANS: Submit button title for 'Yes' when deleting a notice. - _('Delete this notice.')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); - } - - function deleteNotice() - { - // CSRF protection - $token = $this->trimmed('token'); - - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. ' . - 'Try again, please.')); - return; - } - if ($this->arg('yes')) { - if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) { + if (Event::handle('StartDeleteOwnNotice', array($this->scoped->getUser(), $this->notice))) { $this->notice->delete(); - Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice)); + Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice)); } } From e567406c9f9216ee9b1db7b5b190d32205f87664 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 12:08:33 +0200 Subject: [PATCH 032/156] RedirecturlAction now extends ManagedAction --- actions/redirecturl.php | 66 +++++------------------------------------ 1 file changed, 7 insertions(+), 59 deletions(-) diff --git a/actions/redirecturl.php b/actions/redirecturl.php index 4befa4ab84..db3784bf31 100644 --- a/actions/redirecturl.php +++ b/actions/redirecturl.php @@ -28,11 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Redirect to a given URL @@ -47,75 +43,27 @@ if (!defined('STATUSNET')) { * @link http://status.net/ */ -class RedirecturlAction extends Action +class RedirecturlAction extends ManagedAction { - protected $id = null; protected $file = null; - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - function prepare($argarray) + protected function doPreparation() { - parent::prepare($argarray); - - $this->id = $this->trimmed('id'); - - if (empty($this->id)) { - // TRANS: Client exception thrown when no ID parameter was provided. - throw new ClientException(_('No id parameter.')); - } - - $this->file = File::getKV('id', $this->id); - - if (empty($this->file)) { - // TRANS: Client exception thrown when an invalid ID parameter was provided for a file. - // TRANS: %d is the provided ID for which the file is not present (number). - throw new ClientException(sprintf(_('No such file "%d".'), - $this->id), - 404); - } + $this->file = File::getByID($this->int('id')); return true; } - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - function handle($argarray=null) + public function showPage() { common_redirect($this->file->url, 307); } - /** - * Return true if read only. - * - * MAY override - * - * @param array $args other arguments - * - * @return boolean is read only action? - */ function isReadOnly($args) { return true; } - /** - * Return last modified, if applicable. - * - * MAY override - * - * @return string last modified http header - */ function lastModified() { // For comparison with If-Last-Modified @@ -133,9 +81,9 @@ class RedirecturlAction extends Action */ function etag() { - return 'W/"' . implode(':', array($this->arg('action'), + return 'W/"' . implode(':', array($this->getActionName(), common_user_cache_hash(), common_language(), - $this->file->id)) . '"'; + $this->file->getID())) . '"'; } } From f9d82a6ac588bfc5458d063d172513263eac0073 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 12:19:08 +0200 Subject: [PATCH 033/156] Easy comparison of two Profile objects --- classes/Profile.php | 5 +++++ classes/User.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/classes/Profile.php b/classes/Profile.php index f78cd34e36..f628965a74 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -1571,6 +1571,11 @@ class Profile extends Managed_DataObject return $this; } + public function sameAs(Profile $other) + { + return $this->getID() === $other->getID(); + } + /** * This will perform shortenLinks with the connected User object. * diff --git a/classes/User.php b/classes/User.php index 9ae0cba804..72510f162c 100644 --- a/classes/User.php +++ b/classes/User.php @@ -132,6 +132,11 @@ class User extends Managed_DataObject return $this->_profile[$this->id]; } + public function sameAs(Profile $other) + { + return $this->getProfile()->sameAs($other); + } + public function getUri() { return $this->uri; From 6a36121a56fa5ff5e09c0e1454ba4e853fae54b6 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 12:23:55 +0200 Subject: [PATCH 034/156] Spiff up the PersonalTagCloudSection class missing a fix in actions/showstream.php for the switched position of arguments in the constructor --- lib/personaltagcloudsection.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/personaltagcloudsection.php b/lib/personaltagcloudsection.php index c861c4f57f..46b4661e89 100644 --- a/lib/personaltagcloudsection.php +++ b/lib/personaltagcloudsection.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Personal tag cloud section @@ -42,12 +40,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { */ class PersonalTagCloudSection extends TagCloudSection { - var $user = null; + protected $profile = null; - function __construct($out=null, $user=null) + function __construct(Profile $profile, HTMLOutputter $out=null) { parent::__construct($out); - $this->user = $user; + $this->profile = $profile; } function title() @@ -80,7 +78,7 @@ class PersonalTagCloudSection extends TagCloudSection $tag = Memcached_DataObject::cachedQuery('Notice_tag', sprintf($qry, - $this->user->id), + $this->profile->getID()), 3600); return $tag; } From 50c297bcbefc232fe8886f0fe9f4e4952390b3d9 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 12:34:06 +0200 Subject: [PATCH 035/156] ShowstreamAction fixes so it's not as horrible --- actions/all.php | 41 +++++++------------ actions/replies.php | 5 +-- actions/showstream.php | 21 +++++----- lib/noticestreamaction.php | 1 + lib/profileaction.php | 3 -- .../ExtendedProfile/actions/profiledetail.php | 11 ++--- 6 files changed, 32 insertions(+), 50 deletions(-) diff --git a/actions/all.php b/actions/all.php index 39041064f9..19413076b5 100644 --- a/actions/all.php +++ b/actions/all.php @@ -39,8 +39,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } class AllAction extends ShowstreamAction { - var $notice; - public function getStream() { if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) { @@ -54,7 +52,7 @@ class AllAction extends ShowstreamAction function title() { - if (!empty($this->scoped) && $this->scoped->id == $this->target->id) { + if (!empty($this->scoped) && $this->scoped->sameAs($this->target)) { // TRANS: Title of a user's own start page. return _('Home timeline'); } else { @@ -71,44 +69,44 @@ class AllAction extends ShowstreamAction common_local_url( 'ApiTimelineFriends', array( 'format' => 'as', - 'id' => $this->target->nickname + 'id' => $this->target->getNickname() ) ), // TRANS: %s is user nickname. - sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->target->nickname)), + sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->target->getNickname())), new Feed(Feed::RSS1, common_local_url( 'allrss', array( 'nickname' => - $this->target->nickname) + $this->target->getNickname()) ), // TRANS: %s is user nickname. - sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->target->nickname)), + sprintf(_('Feed for friends of %s (RSS 1.0)'), $this->target->getNickname())), new Feed(Feed::RSS2, common_local_url( 'ApiTimelineFriends', array( 'format' => 'rss', - 'id' => $this->target->nickname + 'id' => $this->target->getNickname() ) ), // TRANS: %s is user nickname. - sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->target->nickname)), + sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->target->getNickname())), new Feed(Feed::ATOM, common_local_url( 'ApiTimelineFriends', array( 'format' => 'atom', - 'id' => $this->target->nickname + 'id' => $this->target->getNickname() ) ), // TRANS: %s is user nickname. - sprintf(_('Feed for friends of %s (Atom)'), $this->target->nickname)) + sprintf(_('Feed for friends of %s (Atom)'), $this->target->getNickname())) ); } function showEmptyListMessage() { // TRANS: Empty list message. %s is a user nickname. - $message = sprintf(_('This is the timeline for %s and friends but no one has posted anything yet.'), $this->target->nickname) . ' '; + $message = sprintf(_('This is the timeline for %s and friends but no one has posted anything yet.'), $this->target->getNickname()) . ' '; if (common_logged_in()) { if ($this->target->id === $this->scoped->id) { @@ -118,12 +116,12 @@ class AllAction extends ShowstreamAction } else { // TRANS: %1$s is user nickname, %2$s is user nickname, %2$s is user nickname prefixed with "@". // TRANS: This message contains Markdown links. Keep "](" together. - $message .= sprintf(_('You can try to [nudge %1$s](../%2$s) from their profile or [post something to them](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->target->nickname, $this->target->nickname, '@' . $this->target->nickname); + $message .= sprintf(_('You can try to [nudge %1$s](../%2$s) from their profile or [post something to them](%%%%action.newnotice%%%%?status_textarea=%3$s).'), $this->target->getNickname(), $this->target->getNickname(), '@' . $this->target->getNickname()); } } else { // TRANS: Encouragement displayed on empty timeline user pages for anonymous users. // TRANS: %s is a user nickname. This message contains Markdown links. Keep "](" together. - $message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->target->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'); @@ -134,19 +132,10 @@ class AllAction extends ShowstreamAction function showContent() { if (Event::handle('StartShowAllContent', array($this))) { - - $profile = null; - - $current_user = common_current_user(); - - if (!empty($current_user)) { - $profile = $current_user->getProfile(); - } - - if (!empty($current_user) && $current_user->streamModeOnly()) { + if ($this->scoped instanceof Profile && $this->scoped->isLocal() && $this->scoped->getUser()->streamModeOnly()) { $nl = new PrimaryNoticeList($this->notice, $this, array('show_n'=>NOTICES_PER_PAGE)); } else { - $nl = new ThreadedNoticeList($this->notice, $this, $profile); + $nl = new ThreadedNoticeList($this->notice, $this, $this->scoped); } $cnt = $nl->show(); @@ -157,7 +146,7 @@ class AllAction extends ShowstreamAction $this->pagination( $this->page > 1, $cnt > NOTICES_PER_PAGE, - $this->page, 'all', array('nickname' => $this->target->nickname) + $this->page, 'all', array('nickname' => $this->target->getNickname()) ); Event::handle('EndShowAllContent', array($this)); diff --git a/actions/replies.php b/actions/replies.php index 8baf9d69e0..ae6ec90658 100644 --- a/actions/replies.php +++ b/actions/replies.php @@ -40,9 +40,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class RepliesAction extends ShowstreamAction { - var $page = null; - var $notice; - public function getStream() { return new ReplyNoticeStream($this->target->getID(), $this->scoped); @@ -85,7 +82,7 @@ class RepliesAction extends ShowstreamAction // TRANS: Link for feed with replies for a user. // TRANS: %s is a user nickname. sprintf(_('Replies feed for %s (Activity Streams JSON)'), - $this->user->nickname)), + $this->target->getNickname())), new Feed(Feed::RSS1, common_local_url('repliesrss', array('nickname' => $this->target->getNickname())), diff --git a/actions/showstream.php b/actions/showstream.php index 6eccbd06bf..51384eb487 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -47,16 +47,15 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class ShowstreamAction extends NoticestreamAction { - var $notice; + protected $target = null; protected function doPreparation() { // showstream requires a nickname - $nickname_arg = $this->arg('nickname'); + $nickname_arg = $this->trimmed('nickname'); $nickname = common_canonical_nickname($nickname_arg); // Permanent redirect on non-canonical nickname - if ($nickname_arg != $nickname) { $args = array('nickname' => $nickname); if ($this->arg('page') && $this->arg('page') != 1) { @@ -64,18 +63,20 @@ class ShowstreamAction extends NoticestreamAction } common_redirect(common_local_url($this->getActionName(), $args), 301); } - $this->user = User::getKV('nickname', $nickname); - if (!$this->user) { + try { + $user = User::getByNickname($nickname); + } catch (NoSuchUserException $e) { $group = Local_group::getKV('nickname', $nickname); if ($group instanceof Local_group) { common_redirect($group->getProfile()->getUrl()); } - // TRANS: Client error displayed when calling a profile action without specifying a user. - $this->clientError(_('No such user.'), 404); + + // No user nor group found, throw the NoSuchUserException again + throw $e; } - $this->target = $this->user->getProfile(); + $this->target = $user->getProfile(); } public function getStream() @@ -289,7 +290,7 @@ class ShowstreamAction extends NoticestreamAction { parent::showSections(); if (!common_config('performance', 'high')) { - $cloud = new PersonalTagCloudSection($this, $this->user); + $cloud = new PersonalTagCloudSection($this->target, $this); $cloud->show(); } } @@ -298,7 +299,7 @@ class ShowstreamAction extends NoticestreamAction { $options = parent::noticeFormOptions(); - if (!$this->scoped instanceof Profile || $this->scoped->id != $this->target->id) { + if (!$this->scoped instanceof Profile || !$this->scoped->sameAs($this->target)) { $options['to_profile'] = $this->target; } diff --git a/lib/noticestreamaction.php b/lib/noticestreamaction.php index 39c19d551f..ed8921860e 100644 --- a/lib/noticestreamaction.php +++ b/lib/noticestreamaction.php @@ -4,6 +4,7 @@ if (!defined('GNUSOCIAL')) { exit(1); } abstract class NoticestreamAction extends ProfileAction { + protected $notice = null; // holds the stream result protected function prepare(array $args=array()) { parent::prepare($args); diff --git a/lib/profileaction.php b/lib/profileaction.php index d98bcd7f74..d08446afae 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -58,9 +58,6 @@ abstract class ProfileAction extends ManagedAction throw new ClientException(_('This profile has been silenced by site moderators'), 403); } - // backwards compatibility until all actions are fixed to use $this->target - $this->profile = $this->target; - $this->tag = $this->trimmed('tag'); $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; common_set_returnto($this->selfUrl()); diff --git a/plugins/ExtendedProfile/actions/profiledetail.php b/plugins/ExtendedProfile/actions/profiledetail.php index a777a28e03..ea0b8ad630 100644 --- a/plugins/ExtendedProfile/actions/profiledetail.php +++ b/plugins/ExtendedProfile/actions/profiledetail.php @@ -17,13 +17,10 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } class ProfileDetailAction extends ShowstreamAction { - function isReadOnly($args) { return true; @@ -31,7 +28,7 @@ class ProfileDetailAction extends ShowstreamAction function title() { - return $this->profile->getFancyName(); + return $this->target->getFancyName(); } function showStylesheets() { @@ -43,7 +40,7 @@ class ProfileDetailAction extends ShowstreamAction function showContent() { $cur = common_current_user(); - if ($cur && $cur->id == $this->profile->id) { // your own page + if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->target)) { $this->elementStart('div', 'entity_actions'); $this->elementStart('ul'); $this->elementStart('li', 'entity_edit'); @@ -57,7 +54,7 @@ class ProfileDetailAction extends ShowstreamAction $this->elementEnd('div'); } - $widget = new ExtendedProfileWidget($this, $this->profile); + $widget = new ExtendedProfileWidget($this, $this->target); $widget->show(); } } From f8877e015b1c0a81b34de77642947a57e7507f73 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 12:59:19 +0200 Subject: [PATCH 036/156] static definition of User::getByNickname --- classes/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/User.php b/classes/User.php index 72510f162c..ec6ceccf12 100644 --- a/classes/User.php +++ b/classes/User.php @@ -147,7 +147,7 @@ class User extends Managed_DataObject return $this->getProfile()->getNickname(); } - public function getByNickname($nickname) + static function getByNickname($nickname) { $user = User::getKV('nickname', $nickname); if (!$user instanceof User) { From 9a92b58057fb3a6ae5a6da6d37d2cd3ff4d84bd7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 13:44:47 +0200 Subject: [PATCH 037/156] ShowstreamAction tidying up Lots of these changes mean that we're requiring certain values to either by typed properly or return the expected value. If it doesn't there should be a fatal exception thrown which we can followup in the logs and won't go silently suppressed. --- lib/implugin.php | 14 ++--- lib/profileaction.php | 3 + lib/profilelistitem.php | 11 +++- plugins/GeoURL/GeoURLPlugin.php | 18 +++--- plugins/ModLog/ModLogPlugin.php | 27 ++++----- plugins/OStatus/OStatusPlugin.php | 79 +++++++++++++++------------ plugins/OpenID/OpenIDPlugin.php | 4 +- plugins/WebFinger/WebFingerPlugin.php | 2 +- 8 files changed, 88 insertions(+), 70 deletions(-) diff --git a/lib/implugin.php b/lib/implugin.php index 2da4fa961a..87ca037160 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -563,14 +563,12 @@ abstract class ImPlugin extends Plugin return true; } - function onEndShowHeadElements($action) + function onEndShowHeadElements(Action $action) { - $aname = $action->trimmed('action'); - - if ($aname == 'shownotice') { + if ($action instanceof ShownoticeAction) { $user_im_prefs = new User_im_prefs(); - $user_im_prefs->user_id = $action->profile->id; + $user_im_prefs->user_id = $action->notice->getProfile()->getID(); $user_im_prefs->transport = $this->transport; if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) { @@ -580,13 +578,13 @@ abstract class ImPlugin extends Plugin 'content' => $id->toString())); } - } else if ($aname == 'showstream') { + } elseif ($action instanceof ShowstreamAction) { $user_im_prefs = new User_im_prefs(); - $user_im_prefs->user_id = $action->user->id; + $user_im_prefs->user_id = $action->getTarget()->getID(); $user_im_prefs->transport = $this->transport; - if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->profile->profileurl) { + if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->getTarget()->getUrl()) { $id = new Microid($this->microiduri($user_im_prefs->screenname), $action->selfUrl()); $action->element('meta', array('name' => 'microid', diff --git a/lib/profileaction.php b/lib/profileaction.php index d08446afae..5a5d526e42 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -72,6 +72,9 @@ abstract class ProfileAction extends ManagedAction public function getTarget() { + if (!$this->target instanceof Profile) { + throw new ServerException('No target profile in ProfileAction class'); + } return $this->target; } diff --git a/lib/profilelistitem.php b/lib/profilelistitem.php index e0b94ac76a..e21ff04ebe 100644 --- a/lib/profilelistitem.php +++ b/lib/profilelistitem.php @@ -32,18 +32,25 @@ if (!defined('GNUSOCIAL')) { exit(1); } class ProfileListItem extends Widget { /** Current profile. */ + protected $target = null; var $profile = null; /** Action object using us. */ var $action = null; - function __construct($profile, $action) + function __construct(Profile $target, HTMLOutputter $action) { parent::__construct($action); - $this->profile = $profile; + $this->target = $target; + $this->profile = $this->target; $this->action = $action; } + function getTarget() + { + return $this->target; + } + function show() { if (Event::handle('StartProfileListItem', array($this))) { diff --git a/plugins/GeoURL/GeoURLPlugin.php b/plugins/GeoURL/GeoURLPlugin.php index caad8fde11..a8e2546c4f 100644 --- a/plugins/GeoURL/GeoURLPlugin.php +++ b/plugins/GeoURL/GeoURLPlugin.php @@ -57,25 +57,27 @@ class GeoURLPlugin extends Plugin * * @return boolean event handler flag */ - function onEndShowHeadElements($action) + function onEndShowHeadElements(Action $action) { $name = $action->trimmed('action'); $location = null; - if ($name == 'showstream') { - $profile = $action->profile; - if (!empty($profile) && !empty($profile->lat) && !empty($profile->lon)) { + if ($action instanceof ShowstreamAction) { + $profile = $action->getTarget(); + if (!empty($profile->lat) && !empty($profile->lon)) { $location = $profile->lat . ', ' . $profile->lon; } - } else if ($name == 'shownotice') { - $notice = $action->profile; - if (!empty($notice) && !empty($notice->lat) && !empty($notice->lon)) { + } elseif ($action instanceof ShownoticeAction) { + // FIXME: getNotice in ShownoticeAction will do a new lookup, we should fix those classes + // so they can reliably just get a pre-stored notice object which was fetched in Shownotice prepare()... + $notice = $action->notice; + if ($notice instanceof Notice && !empty($notice->lat) && !empty($notice->lon)) { $location = $notice->lat . ', ' . $notice->lon; } } - if (!empty($location)) { + if (!is_null($location)) { $action->element('meta', array('name' => 'ICBM', 'content' => $location)); $action->element('meta', array('name' => 'DC.title', diff --git a/plugins/ModLog/ModLogPlugin.php b/plugins/ModLog/ModLogPlugin.php index 32c96be0e8..d1e01ca849 100644 --- a/plugins/ModLog/ModLogPlugin.php +++ b/plugins/ModLog/ModLogPlugin.php @@ -101,10 +101,10 @@ class ModLogPlugin extends Plugin $modlog->profile_id = $profile->id; - $cur = common_current_user(); + $scoped = Profile::current(); - if (!empty($cur)) { - $modlog->moderator_id = $cur->id; + if ($scoped instanceof Profile) { + $modlog->moderator_id = $scoped->getID(); } $modlog->role = $role; @@ -118,21 +118,22 @@ class ModLogPlugin extends Plugin function onEndShowSections(Action $action) { - if ($action->arg('action') != 'showstream') { + if (!$action instanceof ShowstreamAction) { + // early return for actions we're not interested in return true; } - $cur = common_current_user(); - - if (empty($cur) || !$cur->hasRight(self::VIEWMODLOG)) { + $scoped = $action->getScoped(); + if (!$scoped instanceof Profile || !$scoped->hasRight(self::VIEWMODLOG)) { + // only continue if we are allowed to VIEWMODLOG return true; } - $profile = $action->profile; + $profile = $action->getTarget(); $ml = new ModLog(); - $ml->profile_id = $profile->id; + $ml->profile_id = $profile->getID(); $ml->orderBy("created"); $cnt = $ml->find(); @@ -152,13 +153,13 @@ class ModLogPlugin extends Plugin $action->element('td', null, sprintf(($ml->is_grant) ? _('+%s') : _('-%s'), $ml->role)); $action->elementStart('td'); if ($ml->moderator_id) { - $mod = Profile::getKV('id', $ml->moderator_id); + $mod = Profile::getByID($ml->moderator_id); if (empty($mod)) { $action->text(_('[unknown]')); } else { - $action->element('a', array('href' => $mod->profileurl, - 'title' => $mod->fullname), - $mod->nickname); + $action->element('a', array('href' => $mod->getUrl(), + 'title' => $mod->getFullname()), + $mod->getNickname()); } } else { $action->text(_('[unknown]')); diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 630031fdde..2fd60ad654 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1063,12 +1063,16 @@ class OStatusPlugin extends Plugin function showEntityRemoteSubscribe($action, $target='ostatussub') { - $user = common_current_user(); - if ($user && ($user->id == $action->profile->id)) { + if (!$action->getScoped() instanceof Profile) { + // early return if we're not logged in + return true; + } + + if ($action->getScoped()->sameAs($action->getTarget())) { $action->elementStart('div', 'entity_actions'); $action->elementStart('p', array('id' => 'entity_remote_subscribe', 'class' => 'entity_subscribe')); - $action->element('a', array('href' => common_local_url($target), + $action->element('a', array('href' => common_local_url($action->getTarget()), 'class' => 'entity_remote_subscribe'), // TRANS: Link text for link to remote subscribe. _m('Remote')); @@ -1127,42 +1131,45 @@ class OStatusPlugin extends Plugin return true; } - function onStartProfileListItemActionElements($item, $profile=null) + // FIXME: This one can accept both an Action and a Widget. Confusing! Refactor to (HTMLOutputter $out, Profile $target)! + function onStartProfileListItemActionElements($item) { - if (!common_logged_in()) { - - $profileUser = User::getKV('id', $item->profile->id); - - if (!empty($profileUser)) { - - if ($item instanceof Action) { - $output = $item; - $profile = $item->profile; - } else { - $output = $item->out; - } - - // Add an OStatus subscribe - $output->elementStart('li', 'entity_subscribe'); - $url = common_local_url('ostatusinit', - array('nickname' => $profileUser->nickname)); - $output->element('a', array('href' => $url, - 'class' => 'entity_remote_subscribe'), - // TRANS: Link text for a user to subscribe to an OStatus user. - _m('Subscribe')); - $output->elementEnd('li'); - - $output->elementStart('li', 'entity_tag'); - $url = common_local_url('ostatustag', - array('nickname' => $profileUser->nickname)); - $output->element('a', array('href' => $url, - 'class' => 'entity_remote_tag'), - // TRANS: Link text for a user to list an OStatus user. - _m('List')); - $output->elementEnd('li'); - } + if (common_logged_in()) { + // only non-logged in users get to see the "remote subscribe" form + return true; + } elseif (!$item->getTarget()->isLocal()) { + // we can (for now) only provide remote subscribe forms for local users + return true; } + if ($item instanceof ProfileAction) { + $output = $item; + } elseif ($item instanceof Widget) { + $output = $item->out; + } else { + // Bad $item class, don't know how to use this for outputting! + throw new ServerException('Bad item type for onStartProfileListItemActionElements'); + } + + // Add an OStatus subscribe + $output->elementStart('li', 'entity_subscribe'); + $url = common_local_url('ostatusinit', + array('nickname' => $item->getTarget()->getNickname())); + $output->element('a', array('href' => $url, + 'class' => 'entity_remote_subscribe'), + // TRANS: Link text for a user to subscribe to an OStatus user. + _m('Subscribe')); + $output->elementEnd('li'); + + $output->elementStart('li', 'entity_tag'); + $url = common_local_url('ostatustag', + array('nickname' => $item->getTarget()->getNickname())); + $output->element('a', array('href' => $url, + 'class' => 'entity_remote_tag'), + // TRANS: Link text for a user to list an OStatus user. + _m('List')); + $output->elementEnd('li'); + return true; } diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 710a34410a..2e9ada9806 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -390,11 +390,11 @@ class OpenIDPlugin extends Plugin $action->element('link', array('rel' => 'openid2.provider', 'href' => common_local_url('openidserver'))); $action->element('link', array('rel' => 'openid2.local_id', - 'href' => $action->profile->profileurl)); + 'href' => $action->getTarget()->getUrl())); $action->element('link', array('rel' => 'openid.server', 'href' => common_local_url('openidserver'))); $action->element('link', array('rel' => 'openid.delegate', - 'href' => $action->profile->profileurl)); + 'href' => $action->getTarget()->getUrl())); } if ($action instanceof SitestreamAction) { diff --git a/plugins/WebFinger/WebFingerPlugin.php b/plugins/WebFinger/WebFingerPlugin.php index 91dc9b53f7..c2a9c69d0c 100644 --- a/plugins/WebFinger/WebFingerPlugin.php +++ b/plugins/WebFinger/WebFingerPlugin.php @@ -144,7 +144,7 @@ class WebFingerPlugin extends Plugin public function onStartShowHTML($action) { if ($action instanceof ShowstreamAction) { - $acct = 'acct:'. $action->profile->nickname .'@'. common_config('site', 'server'); + $acct = 'acct:'. $action->getTarget()->getNickname() .'@'. common_config('site', 'server'); $url = common_local_url('webfinger') . '?resource='.$acct; foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) { From 55b2d124bc90331d8d8c44415407e43dc8a0029a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 22:49:38 +0200 Subject: [PATCH 038/156] The 'target' is an argument to common_local_url not the target profile --- plugins/OStatus/OStatusPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 2fd60ad654..0dace39db0 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1072,7 +1072,7 @@ class OStatusPlugin extends Plugin $action->elementStart('div', 'entity_actions'); $action->elementStart('p', array('id' => 'entity_remote_subscribe', 'class' => 'entity_subscribe')); - $action->element('a', array('href' => common_local_url($action->getTarget()), + $action->element('a', array('href' => common_local_url($target), 'class' => 'entity_remote_subscribe'), // TRANS: Link text for link to remote subscribe. _m('Remote')); From 3900a739b92cf8560ad318fe1106afb9bf8f4fe8 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 22:54:14 +0200 Subject: [PATCH 039/156] Forgotten migrations of ->profile and ->user to ->getTarget() GalleryAction extends ProfileAction which no longer uses ->profile and ->user --- actions/subscriptions.php | 6 ++---- actions/usergroups.php | 16 +++++++-------- plugins/SearchSub/actions/searchsubs.php | 26 ++++++++++-------------- plugins/TagSub/actions/tagsubs.php | 26 ++++++++++-------------- 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/actions/subscriptions.php b/actions/subscriptions.php index 231a697230..b5734b3747 100644 --- a/actions/subscriptions.php +++ b/actions/subscriptions.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * A list of the user's subscriptions @@ -60,7 +58,7 @@ class SubscriptionsAction extends GalleryAction function showPageNotice() { - if ($this->scoped instanceof Profile && $this->scoped->id === $this->target->id) { + if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->getTarget())) { $this->element('p', null, // TRANS: Page notice for page with an overview of all subscriptions // TRANS: of the logged in user's own profile. diff --git a/actions/usergroups.php b/actions/usergroups.php index fd112ba8ec..d4756dffb5 100644 --- a/actions/usergroups.php +++ b/actions/usergroups.php @@ -52,12 +52,12 @@ class UsergroupsAction extends GalleryAction if ($this->page == 1) { // TRANS: Page title for first page of groups for a user. // TRANS: %s is a nickname. - return sprintf(_('%s groups'), $this->user->nickname); + return sprintf(_('%s groups'), $this->getTarget()->getNickname()); } else { // TRANS: Page title for all but the first page of groups for a user. // TRANS: %1$s is a nickname, %2$d is a page number. return sprintf(_('%1$s groups, page %2$d'), - $this->user->nickname, + $this->getTarget()->getNickname(), $this->page); } } @@ -82,14 +82,14 @@ class UsergroupsAction extends GalleryAction $offset = ($this->page-1) * GROUPS_PER_PAGE; $limit = GROUPS_PER_PAGE + 1; - $groups = $this->user->getGroups($offset, $limit); + $groups = $this->getTarget()->getGroups($offset, $limit); if ($groups instanceof User_group) { - $gl = new GroupList($groups, $this->user, $this); + $gl = new GroupList($groups, $this->getTarget(), $this); $cnt = $gl->show(); $this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE, $this->page, 'usergroups', - array('nickname' => $this->user->nickname)); + array('nickname' => $this->getTarget()->getNickname())); } else { $this->showEmptyListMessage(); } @@ -102,11 +102,11 @@ class UsergroupsAction extends GalleryAction { // TRANS: Text on group page for a user that is not a member of any group. // TRANS: %s is a user nickname. - $message = sprintf(_('%s is not a member of any group.'), $this->user->nickname) . ' '; + $message = sprintf(_('%s is not a member of any group.'), $this->getTarget()->getNickname()) . ' '; if (common_logged_in()) { $current_user = common_current_user(); - if ($this->user->id === $current_user->id) { + if ($this->scoped->sameAs($this->getTarget())) { // TRANS: Text on group page for a user that is not a member of any group. This message contains // TRANS: a Markdown link in the form [link text](link) and a variable that should not be changed. $message .= _('Try [searching for groups](%%action.groupsearch%%) and joining them.'); @@ -119,7 +119,7 @@ class UsergroupsAction extends GalleryAction function showProfileBlock() { - $block = new AccountProfileBlock($this, $this->profile); + $block = new AccountProfileBlock($this, $this->getTarget()); $block->show(); } } diff --git a/plugins/SearchSub/actions/searchsubs.php b/plugins/SearchSub/actions/searchsubs.php index 54563ed0e7..fd89075032 100644 --- a/plugins/SearchSub/actions/searchsubs.php +++ b/plugins/SearchSub/actions/searchsubs.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * A list of the user's subscriptions @@ -48,20 +46,19 @@ class SearchSubsAction extends GalleryAction if ($this->page == 1) { // TRANS: Header for subscriptions overview for a user (first page). // TRANS: %s is a user nickname. - return sprintf(_m('%s\'s search subscriptions'), $this->user->nickname); + return sprintf(_m('%s\'s search subscriptions'), $this->getTarget()->getNickname()); } else { // TRANS: Header for subscriptions overview for a user (not first page). // TRANS: %1$s is a user nickname, %2$d is the page number. return sprintf(_m('%1$s\'s search subscriptions, page %2$d'), - $this->user->nickname, + $this->getTarget()->getNickname(), $this->page); } } function showPageNotice() { - $user = common_current_user(); - if ($user && ($user->id == $this->profile->id)) { + if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->getTarget())) { $this->element('p', null, // TRANS: Page notice for page with an overview of all search subscriptions // TRANS: of the logged in user's own profile. @@ -71,7 +68,7 @@ class SearchSubsAction extends GalleryAction // TRANS: Page notice for page with an overview of all subscriptions of a user other // TRANS: than the logged in user. %s is the user nickname. sprintf(_m('%s has subscribed to receive all notices on this site matching the following searches:'), - $this->profile->nickname)); + $this->getTarget()->getNickname())); } } @@ -86,12 +83,12 @@ class SearchSubsAction extends GalleryAction $cnt = 0; $searchsub = new SearchSub(); - $searchsub->profile_id = $this->user->id; + $searchsub->profile_id = $this->getTarget()->getID(); $searchsub->limit($limit, $offset); $searchsub->find(); if ($searchsub->N) { - $list = new SearchSubscriptionsList($searchsub, $this->user, $this); + $list = new SearchSubscriptionsList($searchsub, $this->getTarget(), $this); $cnt = $list->show(); if (0 == $cnt) { $this->showEmptyListMessage(); @@ -102,7 +99,7 @@ class SearchSubsAction extends GalleryAction $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, $this->page, 'searchsubs', - array('nickname' => $this->user->nickname)); + array('nickname' => $this->getTarget()->getNickname())); Event::handle('EndShowTagSubscriptionsContent', array($this)); @@ -112,8 +109,7 @@ class SearchSubsAction extends GalleryAction function showEmptyListMessage() { if (common_logged_in()) { - $current_user = common_current_user(); - if ($this->user->id === $current_user->id) { + if ($this->scoped->sameAs($this->getTarget())) { // TRANS: Search subscription list text when the logged in user has no search subscriptions. $message = _m('You are not subscribed to any text searches right now. You can push the "Subscribe" button ' . 'on any notice text search to automatically receive any public messages on this site that match that ' . @@ -121,13 +117,13 @@ class SearchSubsAction extends GalleryAction } else { // TRANS: Search subscription list text when looking at the subscriptions for a of a user other // TRANS: than the logged in user that has no search subscriptions. %s is the user nickname. - $message = sprintf(_m('%s is not subscribed to any searches.'), $this->user->nickname); + $message = sprintf(_m('%s is not subscribed to any searches.'), $this->getTarget()->getNickname()); } } else { // TRANS: Subscription list text when looking at the subscriptions for a of a user that has none // TRANS: as an anonymous user. %s is the user nickname. - $message = sprintf(_m('%s is not subscribed to any searches.'), $this->user->nickname); + $message = sprintf(_m('%s is not subscribed to any searches.'), $this->getTarget()->getNickname()); } $this->elementStart('div', 'guide'); diff --git a/plugins/TagSub/actions/tagsubs.php b/plugins/TagSub/actions/tagsubs.php index be195250f6..1e927b4fd1 100644 --- a/plugins/TagSub/actions/tagsubs.php +++ b/plugins/TagSub/actions/tagsubs.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * A list of the user's subscriptions @@ -48,20 +46,19 @@ class TagSubsAction extends GalleryAction if ($this->page == 1) { // TRANS: Header for subscriptions overview for a user (first page). // TRANS: %s is a user nickname. - return sprintf(_m('%s\'s tag subscriptions'), $this->user->nickname); + return sprintf(_m('%s\'s tag subscriptions'), $this->getTarget()->getNickname()); } else { // TRANS: Header for subscriptions overview for a user (not first page). // TRANS: %1$s is a user nickname, %2$d is the page number. return sprintf(_m('%1$s\'s tag subscriptions, page %2$d'), - $this->user->nickname, + $this->getTarget()->getNickname(), $this->page); } } function showPageNotice() { - $user = common_current_user(); - if ($user && ($user->id == $this->profile->id)) { + if ($this->scoped instanceof Profile && $this->scoped->sameAs($this->getTarget())) { $this->element('p', null, // TRANS: Page notice for page with an overview of all tag subscriptions // TRANS: of the logged in user's own profile. @@ -71,7 +68,7 @@ class TagSubsAction extends GalleryAction // TRANS: Page notice for page with an overview of all subscriptions of a user other // TRANS: than the logged in user. %s is the user nickname. sprintf(_m('%s has subscribed to receive all notices on this site containing the following tags:'), - $this->profile->nickname)); + $this->getTarget()->getNickname())); } } @@ -86,12 +83,12 @@ class TagSubsAction extends GalleryAction $cnt = 0; $tagsub = new TagSub(); - $tagsub->profile_id = $this->user->id; + $tagsub->profile_id = $this->getTarget()->getID(); $tagsub->limit($limit, $offset); $tagsub->find(); if ($tagsub->N) { - $list = new TagSubscriptionsList($tagsub, $this->user, $this); + $list = new TagSubscriptionsList($tagsub, $this->getTarget(), $this); $cnt = $list->show(); if (0 == $cnt) { $this->showEmptyListMessage(); @@ -102,7 +99,7 @@ class TagSubsAction extends GalleryAction $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE, $this->page, 'tagsubs', - array('nickname' => $this->user->nickname)); + array('nickname' => $this->getTarget()->getNickname())); Event::handle('EndShowTagSubscriptionsContent', array($this)); @@ -112,8 +109,7 @@ class TagSubsAction extends GalleryAction function showEmptyListMessage() { if (common_logged_in()) { - $current_user = common_current_user(); - if ($this->user->id === $current_user->id) { + if ($this->scoped->sameAs($this->getTarget())) { // TRANS: Tag subscription list text when the logged in user has no tag subscriptions. $message = _m('You are not listening to any hash tags right now. You can push the "Subscribe" button ' . 'on any hashtag page to automatically receive any public messages on this site that use that ' . @@ -121,13 +117,13 @@ class TagSubsAction extends GalleryAction } else { // TRANS: Tag subscription list text when looking at the subscriptions for a of a user other // TRANS: than the logged in user that has no tag subscriptions. %s is the user nickname. - $message = sprintf(_m('%s is not following any tags.'), $this->user->nickname); + $message = sprintf(_m('%s is not following any tags.'), $this->getTarget()->getNickname()); } } else { // TRANS: Subscription list text when looking at the subscriptions for a of a user that has none // TRANS: as an anonymous user. %s is the user nickname. - $message = sprintf(_m('%s is not following any tags.'), $this->user->nickname); + $message = sprintf(_m('%s is not following any tags.'), $this->getTarget()->getNickname()); } $this->elementStart('div', 'guide'); From 08bd4fa6a862fa63feca381cc69ce6174a1d0ff1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:09:44 +0200 Subject: [PATCH 040/156] ShowstreamAction no longer has public ->profile --- lib/tagcloudsection.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/tagcloudsection.php b/lib/tagcloudsection.php index 24a3bd98af..80a9042e0f 100644 --- a/lib/tagcloudsection.php +++ b/lib/tagcloudsection.php @@ -115,11 +115,10 @@ class TagCloudSection extends Section function tagUrl($tag) { - if ('showstream' === $this->out->trimmed('action')) { - return common_local_url('showstream', array('nickname' => $this->out->profile->nickname, 'tag' => $tag)); - } else { - return common_local_url('tag', array('tag' => $tag)); + if ($this->out instanceof ShowstreamAction) { + return common_local_url('showstream', array('nickname' => $this->out->getTarget()->getNickname(), 'tag' => $tag)); } + return common_local_url('tag', array('tag' => $tag)); } function divId() From ad453785f08ec7d37067a5b9b25ac71ddcb83c6e Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:10:24 +0200 Subject: [PATCH 041/156] FoafAction now a ManagedAction --- actions/foaf.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/actions/foaf.php b/actions/foaf.php index bcdc86d886..260388ba44 100644 --- a/actions/foaf.php +++ b/actions/foaf.php @@ -17,24 +17,22 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } define('LISTENER', 1); define('LISTENEE', -1); define('BOTH', 0); // @todo XXX: Documentation missing. -class FoafAction extends Action +class FoafAction extends ManagedAction { function isReadOnly($args) { return true; } - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - $nickname_arg = $this->arg('nickname'); if (empty($nickname_arg)) { @@ -69,10 +67,8 @@ class FoafAction extends Action return true; } - function handle($args) + public function showPage() { - parent::handle($args); - header('Content-Type: application/rdf+xml'); $this->startXML(); From 961031bc28714e008497b2ce9f89d2f69f7f2d39 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:19:54 +0200 Subject: [PATCH 042/156] Cleaning up Directory plugin lists --- lib/profilelistitem.php | 3 +- plugins/Directory/lib/sortablegrouplist.php | 31 ++----------------- .../lib/sortablesubscriptionlist.php | 20 ------------ 3 files changed, 5 insertions(+), 49 deletions(-) diff --git a/lib/profilelistitem.php b/lib/profilelistitem.php index e21ff04ebe..6fe7b99c7b 100644 --- a/lib/profilelistitem.php +++ b/lib/profilelistitem.php @@ -37,7 +37,8 @@ class ProfileListItem extends Widget /** Action object using us. */ var $action = null; - function __construct(Profile $target, HTMLOutputter $action) + // FIXME: Directory plugin sends a User_group here, but should send a Profile and handle User_group specifics itself + function __construct($target, HTMLOutputter $action) { parent::__construct($action); diff --git a/plugins/Directory/lib/sortablegrouplist.php b/plugins/Directory/lib/sortablegrouplist.php index 7474d0daa6..ab00068f6d 100644 --- a/plugins/Directory/lib/sortablegrouplist.php +++ b/plugins/Directory/lib/sortablegrouplist.php @@ -27,11 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/subscriptionlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Widget to show a sortable list of subscriptions @@ -44,16 +40,6 @@ require_once INSTALLDIR . '/lib/subscriptionlist.php'; */ class SortableGroupList extends SortableSubscriptionList { - /** Owner of this list */ - var $owner = null; - - function __construct($profile, $owner=null, $action=null) - { - parent::__construct($profile, $owner, $action); - - $this->owner = $owner; - } - function startList() { $this->out->elementStart('table', array('class' => 'profile_list xoxo')); @@ -119,25 +105,14 @@ class SortableGroupList extends SortableSubscriptionList $this->out->elementStart('tbody'); } - function newListItem($profile, $odd) + function newListItem($profile) { - return new SortableGroupListItem($profile, $this->owner, $this->action, $odd); + return new SortableGroupListItem($profile, $this->owner, $this->action); } } class SortableGroupListItem extends SortableSubscriptionListItem { - /** Owner of this list */ - var $owner = null; - - function __construct($profile, $owner, $action, $alt) - { - parent::__construct($profile, $owner, $action, $alt); - - $this->alt = $alt; // is this row alternate? - $this->owner = $owner; - } - function showHomepage() { if (!empty($this->profile->homepage)) { diff --git a/plugins/Directory/lib/sortablesubscriptionlist.php b/plugins/Directory/lib/sortablesubscriptionlist.php index d6df6c64cd..719834bc91 100644 --- a/plugins/Directory/lib/sortablesubscriptionlist.php +++ b/plugins/Directory/lib/sortablesubscriptionlist.php @@ -40,16 +40,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class SortableSubscriptionList extends SubscriptionList { - /** Owner of this list */ - var $owner = null; - - function __construct($profile, $owner=null, $action=null) - { - parent::__construct($profile, $owner, $action); - - $this->owner = $owner; - } - function startList() { $this->out->elementStart('table', array('class' => 'profile_list xoxo')); @@ -132,16 +122,6 @@ class SortableSubscriptionList extends SubscriptionList class SortableSubscriptionListItem extends SubscriptionListItem { - /** Owner of this list */ - var $owner = null; - - function __construct($profile, $owner, $action) - { - parent::__construct($profile, $owner, $action); - - $this->owner = $owner; - } - function startItem() { $attr = array( From 24b1e26406afe1245d07b62422dcd5144d939f70 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:24:50 +0200 Subject: [PATCH 043/156] MagicEnvelope called DOMDocument::loadXML statically but apparently we shouldn't do this, despite recommended on https://secure.php.net/manual/en/domdocument.loadxml.php --- plugins/OStatus/lib/magicenvelope.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 9e02f5eab5..dfd3abaeab 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -50,8 +50,8 @@ class MagicEnvelope */ public function __construct($xml=null) { if (!empty($xml)) { - $dom = DOMDocument::loadXML($xml); - if (!$dom instanceof DOMDocument) { + $dom = new DOMDocument(); + if (!$dom->loadXML($xml)) { throw new ServerException('Tried to load malformed XML as DOM'); } elseif (!$this->fromDom($dom)) { throw new ServerException('Could not load MagicEnvelope from DOM'); From 64fbc9321753a89f7bdcebf900e0c698cd64c86b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:30:17 +0200 Subject: [PATCH 044/156] ApiAction::dateTwitter was called statically from a plugin --- lib/apiaction.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index 724447f120..cd2207b378 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -248,7 +248,7 @@ class ApiAction extends Action $twitter_user['friends_count'] = $profile->subscriptionCount(); - $twitter_user['created_at'] = $this->dateTwitter($profile->created); + $twitter_user['created_at'] = self::dateTwitter($profile->created); $timezone = 'UTC'; @@ -322,7 +322,7 @@ class ApiAction extends Action $twitter_status = array(); $twitter_status['text'] = $notice->content; $twitter_status['truncated'] = false; # Not possible on StatusNet - $twitter_status['created_at'] = $this->dateTwitter($notice->created); + $twitter_status['created_at'] = self::dateTwitter($notice->created); try { // We could just do $notice->reply_to but maybe the future holds a // different story for parenting. @@ -440,8 +440,8 @@ class ApiAction extends Action $twitter_group['homepage'] = $group->homepage; $twitter_group['description'] = $group->description; $twitter_group['location'] = $group->location; - $twitter_group['created'] = $this->dateTwitter($group->created); - $twitter_group['modified'] = $this->dateTwitter($group->modified); + $twitter_group['created'] = self::dateTwitter($group->created); + $twitter_group['modified'] = self::dateTwitter($group->modified); return $twitter_group; } @@ -1196,7 +1196,7 @@ class ApiAction extends Action $this->endDocument('xml'); } - function dateTwitter($dt) + static function dateTwitter($dt) { $dateStr = date('d F Y H:i:s', strtotime($dt)); $d = new DateTime($dateStr, new DateTimeZone('UTC')); From 76396041e2444f390d692131c8b02140c290076b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 10 Jul 2015 23:30:59 +0200 Subject: [PATCH 045/156] $notices and $notice is not the same, silly! --- classes/Notice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index 5fefa6c4ef..ae722138b3 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1230,7 +1230,7 @@ class Notice extends Managed_DataObject } $stream = new ConversationNoticeStream($this->conversation); - $notices = $stream->getNotices(/*offset*/ 1, /*limit*/ 1); + $notice = $stream->getNotices(/*offset*/ 1, /*limit*/ 1); // if our "offset 1, limit 1" query got a result, return true else false return $notice->N > 0; From 5929b629e586fd1a44c0441f527a143db87d7807 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 00:08:23 +0200 Subject: [PATCH 046/156] define Subscription_queue::exists as static --- classes/Subscription_queue.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/Subscription_queue.php b/classes/Subscription_queue.php index 878fcf5796..a60293e215 100644 --- a/classes/Subscription_queue.php +++ b/classes/Subscription_queue.php @@ -47,10 +47,10 @@ class Subscription_queue extends Managed_DataObject return $rq; } - public function exists(Profile $subscriber, Profile $other) + static function exists(Profile $subscriber, Profile $other) { - $sub = Subscription_queue::pkeyGet(array('subscriber' => $subscriber->id, - 'subscribed' => $other->id)); + $sub = Subscription_queue::pkeyGet(array('subscriber' => $subscriber->getID(), + 'subscribed' => $other->getID())); return ($sub instanceof Subscription_queue); } From bb29d180177758f5b3a05bb05c9c793d7a9ba47b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 00:28:32 +0200 Subject: [PATCH 047/156] ShowfavoritesAction now extends ShowstreamAction --- plugins/Favorite/actions/showfavorites.php | 114 ++------------------- 1 file changed, 9 insertions(+), 105 deletions(-) diff --git a/plugins/Favorite/actions/showfavorites.php b/plugins/Favorite/actions/showfavorites.php index 98a6bfc5bf..cba29063c2 100644 --- a/plugins/Favorite/actions/showfavorites.php +++ b/plugins/Favorite/actions/showfavorites.php @@ -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,119 +38,29 @@ 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 ShowfavoritesAction extends Action +class ShowfavoritesAction extends ShowstreamAction { - /** User we're getting the faves of */ - var $user = null; - /** Page of the faves we're on */ - var $page = null; - - /** - * Is this a read-only page? - * - * @return boolean true - */ - function isReadOnly($args) - { - return true; - } - - /** - * Title of the page - * - * Includes name of user and page number. - * - * @return string title of page - */ function title() { if ($this->page == 1) { // TRANS: Title for first page of favourite notices of a user. // TRANS: %s is the user for whom the favourite notices are displayed. - return sprintf(_('%s\'s favorite notices'), $this->user->nickname); + return sprintf(_('%s\'s favorite notices'), $this->getTarget()->getNickname()); } else { // TRANS: Title for all but the first page of favourite notices of a user. // TRANS: %1$s is the user for whom the favourite notices are displayed, %2$d is the page number. return sprintf(_('%1$s\'s favorite notices, page %2$d'), - $this->user->nickname, + $this->getTarget()->getNickname(), $this->page); } } - /** - * 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) + public function getStream() { - parent::prepare($args); - - $nickname = common_canonical_nickname($this->arg('nickname')); - - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - // TRANS: Client error displayed when trying to display favourite notices for a non-existing user. - $this->clientError(_('No such user.')); - } - - $this->page = $this->trimmed('page'); - - if (!$this->page) { - $this->page = 1; - } - - common_set_returnto($this->selfUrl()); - - $cur = common_current_user(); - - // Show imported/gateway notices as well as local if - // the user is looking at their own favorites, otherwise not. - $this->notice = Fave::stream($this->user->id, - ($this->page-1)*NOTICES_PER_PAGE, // offset - NOTICES_PER_PAGE + 1, // limit - (!empty($cur) && $cur->id == $this->user->id) // own feed? - ); - - if (empty($this->notice)) { - // TRANS: Server error displayed when favourite notices could not be retrieved from the database. - $this->serverError(_('Could not retrieve favorite notices.')); - } - - if($this->page > 1 && $this->notice->N == 0){ - // TRANS: Client error when page not found (404) - $this->clientError(_('No such page.'), 404); - } - - return true; + $own = $this->scoped instanceof Profile ? $this->scoped->sameAs($this->getTarget()) : false; + return new FaveNoticeStream($this->getTarget()->getID(), $own); } - /** - * 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(); - } - - /** - * Feeds for the section - * - * @return array Feed objects to show - */ function getFeeds() { return array(new Feed(Feed::JSON, @@ -223,7 +127,7 @@ class ShowfavoritesAction extends Action * * @return void */ - function showContent() + function showNotices() { $nl = new FavoritesNoticeList($this->notice, $this); @@ -234,7 +138,7 @@ class ShowfavoritesAction extends Action $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, $this->page, 'showfavorites', - array('nickname' => $this->user->nickname)); + array('nickname' => $this->getTarget()->getNickname())); } function showPageNotice() { From fae79b581223e45a92c5df35306a00eb2ab6a47b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 00:32:05 +0200 Subject: [PATCH 048/156] Forgot to push TargetedRss10Action --- lib/targetedrss10action.php | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/targetedrss10action.php diff --git a/lib/targetedrss10action.php b/lib/targetedrss10action.php new file mode 100644 index 0000000000..c7615bd36a --- /dev/null +++ b/lib/targetedrss10action.php @@ -0,0 +1,46 @@ +. + * + * @category Mail + * @package StatusNet + * @author Evan Prodromou + * @author Earle Martin + * @copyright 2008-9 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class TargetedRss10Action extends Rss10Action +{ + protected $target = null; + + protected function doStreamPreparation() + { + $this->target = User::getByNickname($this->trimmed('nickname'))->getProfile(); + } + + function getImage() + { + return $this->target->avatarUrl(AVATAR_PROFILE_SIZE); + } +} From e439ace944a053f760ef13dd469fd822da3e8eec Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 01:00:04 +0200 Subject: [PATCH 049/156] bump alpha number to ease remote debugging help --- lib/framework.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/framework.php b/lib/framework.php index da43297d10..fec265fd1e 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -23,7 +23,7 @@ define('GNUSOCIAL_ENGINE', 'GNU social'); define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/'); define('GNUSOCIAL_BASE_VERSION', '1.2.0'); -define('GNUSOCIAL_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' +define('GNUSOCIAL_LIFECYCLE', 'alpha2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE); From 9101a1db3d6b369bf09590e8de21d66996c3eda1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 11:09:16 +0200 Subject: [PATCH 050/156] No limit argument to Rss10Action->getNotices() (use $this->limit) --- lib/rss10action.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rss10action.php b/lib/rss10action.php index 7b6eed9c96..744d1e4b7a 100644 --- a/lib/rss10action.php +++ b/lib/rss10action.php @@ -107,7 +107,7 @@ class Rss10Action extends ManagedAction * @return array an array of Notice objects sorted in reverse chron */ - protected function getNotices($limit=0) + protected function getNotices() { return array(); } From 7d524307d247d15687065be75f4524bc9e638a2b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 11:16:08 +0200 Subject: [PATCH 051/156] DeletenoticeForm is its own class now --- actions/deletenotice.php | 6 ++-- lib/deletenoticeform.php | 67 ++++++++++++++++++++++++++++++++++++++++ lib/formaction.php | 2 +- 3 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 lib/deletenoticeform.php diff --git a/actions/deletenotice.php b/actions/deletenotice.php index 8f0211f8f9..d8037ab444 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -33,15 +33,13 @@ if (!defined('GNUSOCIAL')) { exit(1); } // @todo FIXME: documentation needed. class DeletenoticeAction extends FormAction { - protected $form = 'deletenotice'; - protected $notice = null; protected function doPreparation() { - $this->notice = Notice::getByID($this->int('notice')); + $this->notice = Notice::getByID($this->trimmed('notice')); - if ($this->notice->profile_id != $this->scoped->getID() && + if (!$this->scoped->sameAs($this->notice->getProfile()) && !$this->scoped->hasRight(Right::DELETEOTHERSNOTICE)) { // TRANS: Error message displayed trying to delete a notice that was not made by the current user. $this->clientError(_('Cannot delete this notice.')); diff --git a/lib/deletenoticeform.php b/lib/deletenoticeform.php new file mode 100644 index 0000000000..d06004613a --- /dev/null +++ b/lib/deletenoticeform.php @@ -0,0 +1,67 @@ +notice = $formOpts['notice']; + } + + function id() + { + return 'form_notice_delete-' . $this->notice->getID(); + } + + function formClass() + { + return 'form_settings'; + } + + function action() + { + return common_local_url('deletenotice', array('notice' => $this->notice->getID())); + } + + function formLegend() + { + $this->out->element('legend', null, _('Delete notice')); + } + + function formData() + { + $this->out->element('p', null, _('Are you sure you want to delete this notice?')); + } + + /** + * Action elements + * + * @return void + */ + function formActions() + { + $this->out->submit('form_action-no', + // TRANS: Button label on the delete notice form. + _m('BUTTON','No'), + 'submit form_action-primary', + 'no', + // TRANS: Submit button title for 'No' when deleting a notice. + _('Do not delete this notice.')); + $this->out->submit('form_action-yes', + // TRANS: Button label on the delete notice form. + _m('BUTTON','Yes'), + 'submit form_action-secondary', + 'yes', + // TRANS: Submit button title for 'Yes' when deleting a notice. + _('Delete this notice.')); + } +} diff --git a/lib/formaction.php b/lib/formaction.php index 5cf05d4a32..7d74fc4713 100644 --- a/lib/formaction.php +++ b/lib/formaction.php @@ -50,7 +50,7 @@ class FormAction extends ManagedAction protected function prepare(array $args=array()) { parent::prepare($args); - $this->form = $this->form ?: $this->action; + $this->form = $this->form ?: ucfirst($this->action); $this->args['form'] = $this->form; $this->type = !is_null($this->type) ? $this->type : $this->trimmed('type'); From ac986006405593ad1788280a173096b22e9b55cc Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 11:26:48 +0200 Subject: [PATCH 052/156] More RESTish URL (/notice/:notice/delete) for notice delete Also returns to 'top' now after notice deletion. --- actions/deletenotice.php | 10 ++-------- lib/router.php | 4 +--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/actions/deletenotice.php b/actions/deletenotice.php index d8037ab444..40b276a348 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -68,16 +68,10 @@ class DeletenoticeAction extends FormAction $this->notice->delete(); Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice)); } - } - - $url = common_get_returnto(); - - if ($url) { - common_set_returnto(null); } else { - $url = common_local_url('public'); + common_redirect(common_get_returnto(), 303); } - common_redirect($url, 303); + common_redirect(common_local_url('top'), 303); } } diff --git a/lib/router.php b/lib/router.php index 249e5e8823..b13c51c328 100644 --- a/lib/router.php +++ b/lib/router.php @@ -244,12 +244,10 @@ class Router array('action' => 'shownotice'), array('notice' => '[0-9]+')); - $m->connect('notice/delete/:notice', + $m->connect('notice/:notice/delete', array('action' => 'deletenotice'), array('notice' => '[0-9]+')); - $m->connect('notice/delete', array('action' => 'deletenotice')); - // conversation $m->connect('conversation/:id', From c5a5eaf288a55539f91bd51e980770b9a2d82087 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 19:46:01 +0200 Subject: [PATCH 053/156] Do we update feeduri and salmonuri for Ostatus_profile now? When changing from HTTP to HTTPS, following up on commit 59763ceecb33ebf842829cac15f922fa19047de2 where http to https Ostatus_profile URI changing was first introduced. --- plugins/OStatus/classes/Ostatus_profile.php | 32 +++++++++++++++++++++ plugins/OStatus/lib/salmonaction.php | 7 +---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index cb961dc96b..1c4428b16d 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -1977,6 +1977,38 @@ class Ostatus_profile extends Managed_DataObject return $oprofile->localProfile(); } + + public function updateUriKeys($profile_uri, array $hints=array()) + { + $orig = clone($this); + + common_debug('URIFIX These identities both say they are each other: "'.$orig->uri.'" and "'.$profile_uri.'"'); + $this->uri = $profile_uri; + + if (array_key_exists('feedurl', $hints)) { + if (!empty($this->feeduri)) { + common_debug('URIFIX Changing FeedSub ['.$feedsub->id.'] feeduri "'.$feedsub->uri.'" to "'.$hints['feedurl']); + $feedsub = FeedSub::getKV('uri', $this->feeduri); + $feedorig = clone($feedsub); + $feedsub->uri = $hints['feedurl']; + $feedsub->updateWithKeys($feedorig); + } else { + common_debug('URIFIX Old Ostatus_profile did not have feedurl set, ensuring feed: '.$hints['feedurl']); + FeedSub::ensureFeed($hints['feedurl']); + } + $this->feeduri = $hints['feedurl']; + } + if (array_key_exists('salmon', $hints)) { + common_debug('URIFIX Changing Ostatus_profile salmonuri from "'.$this->salmonuri.'" to "'.$hints['salmon'].'"'); + $this->salmonuri = $hints['salmon']; + } + + common_debug('URIFIX Updating Ostatus_profile URI for '.$orig->uri.' to '.$this->uri); + $this->updateWithKeys($orig, 'uri'); // 'uri' is the primary key column + + common_debug('URIFIX Subscribing/renewing feedsub for Ostatus_profile '.$this->uri); + $this->subscribe(); + } } /** diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 5193d302f1..320ea6cdfa 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -259,12 +259,7 @@ class SalmonAction extends Action // Step 4: Is the newly introduced https://example.com/user/1 URI in the list of aliases // presented by http://example.com/user/1 (i.e. do they both say they are the same identity?) if (in_array($e->object_uri, $doublecheck_aliases)) { - common_debug('URIFIX These identities both say they are each other: "'.$aliased_uri.'" and "'.$e->object_uri.'"'); - $orig = clone($oprofile); - $oprofile->uri = $e->object_uri; - common_debug('URIFIX Updating Ostatus_profile URI for '.$aliased_uri.' to '.$oprofile->uri); - $oprofile->updateWithKeys($orig, 'uri'); // 'uri' is the primary key column - unset($orig); + $oprofile->updateUriKeys($e->object_uri, DiscoveryHints::fromXRD($xrd)); $this->oprofile = $oprofile; break; // don't iterate through aliases anymore } From e868ac41cd8221c45dfed56a43d155fb2307a7e4 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 11 Jul 2015 19:48:18 +0200 Subject: [PATCH 054/156] userrss action didn't call parent preparation method --- actions/userrss.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actions/userrss.php b/actions/userrss.php index fd49a0e899..7bed1dd256 100644 --- a/actions/userrss.php +++ b/actions/userrss.php @@ -27,18 +27,20 @@ class UserrssAction extends TargetedRss10Action protected function doStreamPreparation() { + parent::doStreamPreparation(); + $this->tag = $this->trimmed('tag'); } protected function getNotices() { if (!empty($this->tag)) { - $stream = $this->target->getTaggedNotices($this->tag, 0, $this->limit); + $stream = $this->getTarget()->getTaggedNotices($this->tag, 0, $this->limit); return $stream->fetchAll(); } // otherwise we fetch a normal user stream - $stream = $this->target->getNotices(0, $this->limit); + $stream = $this->getTarget()->getNotices(0, $this->limit); return $stream->fetchAll(); } From 01a4ab30dc1dfbf4d6ee6a42a7f8f45a1a42894d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 14 Jul 2015 16:52:20 +0200 Subject: [PATCH 055/156] Removing MicroID as well as simplifying profileaction sorry, forgot to commit in between --- actions/emailsettings.php | 8 --- actions/groupbyid.php | 7 +-- actions/imsettings.php | 4 +- actions/shownotice.php | 15 +----- actions/showstream.php | 39 --------------- classes/User.php | 4 -- classes/User_im_prefs.php | 2 - lib/galleryaction.php | 29 ----------- lib/implugin.php | 24 --------- lib/microid.php | 97 ------------------------------------- lib/profileaction.php | 35 +++++++++++-- plugins/Xmpp/XmppPlugin.php | 5 -- 12 files changed, 33 insertions(+), 236 deletions(-) delete mode 100644 lib/microid.php diff --git a/actions/emailsettings.php b/actions/emailsettings.php index a0f111c0d5..cffc5da247 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -231,12 +231,6 @@ class EmailsettingsAction extends SettingsAction _('Allow friends to nudge me and send me an email.'), $user->emailnotifynudge); $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('emailmicroid', - // TRANS: Checkbox label in e-mail preferences form. - _('Publish a MicroID for my email address.'), - $user->emailmicroid); - $this->elementEnd('li'); Event::handle('EndEmailFormData', array($this, $this->scoped)); } $this->elementEnd('ul'); @@ -320,7 +314,6 @@ class EmailsettingsAction extends SettingsAction $emailnotifymsg = $this->booleanintstring('emailnotifymsg'); $emailnotifynudge = $this->booleanintstring('emailnotifynudge'); $emailnotifyattn = $this->booleanintstring('emailnotifyattn'); - $emailmicroid = $this->booleanintstring('emailmicroid'); $emailpost = $this->booleanintstring('emailpost'); $user->query('BEGIN'); @@ -331,7 +324,6 @@ class EmailsettingsAction extends SettingsAction $user->emailnotifymsg = $emailnotifymsg; $user->emailnotifynudge = $emailnotifynudge; $user->emailnotifyattn = $emailnotifyattn; - $user->emailmicroid = $emailmicroid; $user->emailpost = $emailpost; $result = $user->update($original); diff --git a/actions/groupbyid.php b/actions/groupbyid.php index b82a861e97..8556675155 100644 --- a/actions/groupbyid.php +++ b/actions/groupbyid.php @@ -28,12 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/noticelist.php'; -require_once INSTALLDIR.'/lib/feedlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Permalink for a group diff --git a/actions/imsettings.php b/actions/imsettings.php index 92fff45a7d..feb5d32fc1 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -179,8 +179,6 @@ class ImsettingsAction extends SettingsAction // TRANS: Checkbox label in IM preferences form. array('name'=>'replies', 'description'=>_('Send me replies '. 'from people I\'m not subscribed to.')), - // TRANS: Checkbox label in IM preferences form. - array('name'=>'microid', 'description'=>_('Publish a MicroID')) ); foreach($preferences as $preference) { @@ -277,7 +275,7 @@ class ImsettingsAction extends SettingsAction $user_im_prefs->user_id = $user->id; if($user_im_prefs->find() && $user_im_prefs->fetch()) { - $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); + $preferences = array('notify', 'updatefrompresence', 'replies'); do { $original = clone($user_im_prefs); diff --git a/actions/shownotice.php b/actions/shownotice.php index 4a1adfd7de..23386868dd 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -222,25 +222,12 @@ class ShownoticeAction extends ManagedAction /** * Extra content * - * We show the microid(s) for the author, if any. + * Facebook OpenGraph metadata. * * @return void */ function extraHead() { - $user = User::getKV($this->profile->id); - - if (!$user instanceof User) { - return; - } - - if ($user->emailmicroid && $user->email && $this->notice->uri) { - $id = new Microid('mailto:'. $user->email, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } - // Extras to aid in sharing notices to Facebook $avatarUrl = $this->profile->avatarUrl(AVATAR_PROFILE_SIZE); $this->element('meta', array('property' => 'og:image', diff --git a/actions/showstream.php b/actions/showstream.php index 51384eb487..890c1e711b 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -47,38 +47,6 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class ShowstreamAction extends NoticestreamAction { - protected $target = null; - - protected function doPreparation() - { - // showstream requires a nickname - $nickname_arg = $this->trimmed('nickname'); - $nickname = common_canonical_nickname($nickname_arg); - - // Permanent redirect on non-canonical nickname - if ($nickname_arg != $nickname) { - $args = array('nickname' => $nickname); - if ($this->arg('page') && $this->arg('page') != 1) { - $args['page'] = $this->arg['page']; - } - common_redirect(common_local_url($this->getActionName(), $args), 301); - } - - try { - $user = User::getByNickname($nickname); - } catch (NoSuchUserException $e) { - $group = Local_group::getKV('nickname', $nickname); - if ($group instanceof Local_group) { - common_redirect($group->getProfile()->getUrl()); - } - - // No user nor group found, throw the NoSuchUserException again - throw $e; - } - - $this->target = $user->getProfile(); - } - public function getStream() { if (empty($this->tag)) { @@ -195,13 +163,6 @@ class ShowstreamAction extends NoticestreamAction 'content' => $this->target->getDescription())); } - if ($this->target->isLocal() && $this->target->getUser()->emailmicroid && $this->target->getUser()->email && $this->target->getUrl()) { - $id = new Microid('mailto:'.$this->target->getUser()->email, - $this->selfUrl()); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } - // See https://wiki.mozilla.org/Microsummaries $this->element('link', array('rel' => 'microsummary', diff --git a/classes/User.php b/classes/User.php index ec6ceccf12..e1ce33be18 100644 --- a/classes/User.php +++ b/classes/User.php @@ -42,7 +42,6 @@ class User extends Managed_DataObject public $emailnotifynudge; // tinyint(1) default_1 public $emailnotifymsg; // tinyint(1) default_1 public $emailnotifyattn; // tinyint(1) default_1 - public $emailmicroid; // tinyint(1) default_1 public $language; // varchar(50) public $timezone; // varchar(50) public $emailpost; // tinyint(1) default_1 @@ -77,7 +76,6 @@ class User extends Managed_DataObject 'emailnotifynudge' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of nudges'), 'emailnotifymsg' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of direct messages'), 'emailnotifyattn' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Notify by email of @-replies'), - 'emailmicroid' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'whether to publish email microid'), 'language' => array('type' => 'varchar', 'length' => 50, 'description' => 'preferred language'), 'timezone' => array('type' => 'varchar', 'length' => 50, 'description' => 'timezone'), 'emailpost' => array('type' => 'int', 'size' => 'tiny', 'default' => 1, 'description' => 'Post by email'), @@ -276,9 +274,7 @@ class User extends Managed_DataObject $user->emailnotifynudge = 1; $user->emailnotifymsg = 1; $user->emailnotifyattn = 1; - $user->emailmicroid = 1; $user->emailpost = 1; - $user->jabbermicroid = 1; $user->created = common_sql_now(); diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php index 16fd030bb4..6a5fc7f3ab 100644 --- a/classes/User_im_prefs.php +++ b/classes/User_im_prefs.php @@ -40,7 +40,6 @@ class User_im_prefs extends Managed_DataObject 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) public $updatefrompresence; // tinyint(1) public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 public $modified; // timestamp not_null default_CURRENT_TIMESTAMP @@ -57,7 +56,6 @@ class User_im_prefs extends Managed_DataObject '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'), 'updatefrompresence' => array('type' => 'int', 'size' => 'tiny', 'not null' => true, 'default' => 0, 'description' => 'Send replies from people not subscribed 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'), diff --git a/lib/galleryaction.php b/lib/galleryaction.php index f87043ac06..5dd0cfcfa7 100644 --- a/lib/galleryaction.php +++ b/lib/galleryaction.php @@ -36,35 +36,6 @@ class GalleryAction extends ProfileAction parent::handle(); } - protected function doPreparation() - { - // showstream requires a nickname - $nickname_arg = $this->arg('nickname'); - $nickname = common_canonical_nickname($nickname_arg); - - // Permanent redirect on non-canonical nickname - - if ($nickname_arg != $nickname) { - $args = array('nickname' => $nickname); - if ($this->arg('page') && $this->arg('page') != 1) { - $args['page'] = $this->arg['page']; - } - common_redirect(common_local_url($this->getActionName(), $args), 301); - } - $this->user = User::getKV('nickname', $nickname); - - if (!$this->user) { - $group = Local_group::getKV('nickname', $nickname); - if ($group instanceof Local_group) { - common_redirect($group->getProfile()->getUrl()); - } - // TRANS: Client error displayed when calling a profile action without specifying a user. - $this->clientError(_('No such user.'), 404); - } - - $this->target = $this->user->getProfile(); - } - function showContent() { $this->showTagsDropdown(); diff --git a/lib/implugin.php b/lib/implugin.php index 87ca037160..6395ecbdb7 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -126,17 +126,6 @@ abstract class ImPlugin extends Plugin */ abstract function daemonScreenname(); - /** - * get the microid uri of a given screenname - * - * @param string $screenname screenname - * - * @return string microid uri - */ - function microiduri($screenname) - { - return $this->transport . ':' . $screenname; - } //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\ /** @@ -571,25 +560,12 @@ abstract class ImPlugin extends Plugin $user_im_prefs->user_id = $action->notice->getProfile()->getID(); $user_im_prefs->transport = $this->transport; - if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) { - $id = new Microid($this->microiduri($user_im_prefs->screenname), - $action->notice->uri); - $action->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } - } elseif ($action instanceof ShowstreamAction) { $user_im_prefs = new User_im_prefs(); $user_im_prefs->user_id = $action->getTarget()->getID(); $user_im_prefs->transport = $this->transport; - if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->getTarget()->getUrl()) { - $id = new Microid($this->microiduri($user_im_prefs->screenname), - $action->selfUrl()); - $action->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } } } diff --git a/lib/microid.php b/lib/microid.php deleted file mode 100644 index e2e7d7607f..0000000000 --- a/lib/microid.php +++ /dev/null @@ -1,97 +0,0 @@ -. - * - * @category ID - * @package StatusNet - * @author Evan Prodromou - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * A class for microids - * - * @category ID - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * @see http://microid.org/ - */ - -class Microid -{ - /** Agent part of the ID. */ - - var $agent = null; - - /** Resource part of the ID. */ - - var $resource = null; - - /** - * Constructor - * - * @param string $agent Agent of the ID - * @param string $resource Resource part - */ - - function __construct($agent, $resource) - { - $this->agent = $agent; - $this->resource = $resource; - - } - - /** - * Generate a MicroID string - * - * @return string MicroID for agent and resource - */ - - function toString() - { - $agent_proto = $this->_getProto($this->agent); - $resource_proto = $this->_getProto($this->resource); - - return $agent_proto.'+'.$resource_proto.':sha1:'. - sha1(sha1($this->agent).sha1($this->resource)); - } - - /** - * Utility for getting the protocol part of a URI - * - * @param string $uri URI to parse - * - * @return string scheme part of the URI - */ - - function _getProto($uri) - { - $colon = strpos($uri, ':'); - return substr($uri, 0, $colon); - } -} diff --git a/lib/profileaction.php b/lib/profileaction.php index 5a5d526e42..3dc28a7cc4 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -48,6 +48,36 @@ abstract class ProfileAction extends ManagedAction protected $target = null; // Profile that we're showing + protected function doPreparation() + { + // showstream requires a nickname + $nickname_arg = $this->trimmed('nickname'); + $nickname = common_canonical_nickname($nickname_arg); + + // Permanent redirect on non-canonical nickname + if ($nickname_arg != $nickname) { + $args = array('nickname' => $nickname); + if ($this->arg('page') && $this->arg('page') != 1) { + $args['page'] = $this->arg['page']; + } + common_redirect(common_local_url($this->getActionName(), $args), 301); + } + + try { + $user = User::getByNickname($nickname); + } catch (NoSuchUserException $e) { + $group = Local_group::getKV('nickname', $nickname); + if ($group instanceof Local_group) { + common_redirect($group->getProfile()->getUrl()); + } + + // No user nor group found, throw the NoSuchUserException again + throw $e; + } + + $this->target = $user->getProfile(); + } + protected function prepare(array $args=array()) { // this will call ->doPreparation() which child classes use to set $this->target @@ -65,11 +95,6 @@ abstract class ProfileAction extends ManagedAction return true; } - protected function profileActionPreparation() - { - // Nothing to do by default. - } - public function getTarget() { if (!$this->target instanceof Profile) { diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index 0a70268735..04101a8e2b 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -308,11 +308,6 @@ class XmppPlugin extends ImPlugin return true; } - function microiduri($screenname) - { - return 'xmpp:' . $screenname; - } - function sendMessage($screenname, $body) { $this->queuedConnection()->message($screenname, $body, 'chat'); From cd23c78800b0e2b80200f8e4b1156190330f39c9 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 15 Jul 2015 19:21:21 +0200 Subject: [PATCH 056/156] Less redundant code. --- classes/Memcached_DataObject.php | 2 +- classes/Notice.php | 22 ++++++---------------- lib/util.php | 28 ++++++++++------------------ 3 files changed, 17 insertions(+), 35 deletions(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index c1f6f644db..41ce715210 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -74,7 +74,7 @@ class Memcached_DataObject extends Safe_DataObject { $obj = new $cls; - // php-compatible, for settype(), datatype + // PHP compatible datatype for settype() below $colType = $obj->columnType($keyCol); if (!in_array($colType, array('integer', 'int'))) { diff --git a/classes/Notice.php b/classes/Notice.php index ae722138b3..6301f9ce62 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1656,32 +1656,22 @@ class Notice extends Managed_DataObject protected $_replies = array(); /** - * Pull the complete list of @-reply targets for this notice. + * Pull the complete list of @-mentioned profile IDs for this notice. * * @return array of integer profile ids */ function getReplies() { - if (isset($this->_replies[$this->id])) { - return $this->_replies[$this->id]; + if (!isset($this->_replies[$this->getID()])) { + $mentions = Reply::multiGet('notice_id', array($this->getID())); + $this->_replies[$this->getID()] = $mentions->fetchAll('profile_id'); } - - $replyMap = Reply::listGet('notice_id', array($this->id)); - - $ids = array(); - - foreach ($replyMap[$this->id] as $reply) { - $ids[] = $reply->profile_id; - } - - $this->_replies[$this->id] = $ids; - - return $ids; + return $this->_replies[$this->getID()]; } function _setReplies($replies) { - $this->_replies[$this->id] = $replies; + $this->_replies[$this->getID()] = $replies; } /** diff --git a/lib/util.php b/lib/util.php index f29d9559b9..4d69651089 100644 --- a/lib/util.php +++ b/lib/util.php @@ -710,25 +710,17 @@ function common_find_mentions($text, Notice $notice) // Is it a reply? - if ($notice instanceof Notice) { + $origNotice = $notice->getParent(); + $origAuthor = $origNotice->getProfile(); + + $ids = $origNotice->getReplies(); + + foreach ($ids as $id) { try { - $origNotice = $notice->getParent(); - $origAuthor = $origNotice->getProfile(); - - $ids = $origNotice->getReplies(); - - foreach ($ids as $id) { - $repliedTo = Profile::getKV('id', $id); - if ($repliedTo instanceof Profile) { - $origMentions[$repliedTo->nickname] = $repliedTo; - } - } - } catch (NoProfileException $e) { - common_log(LOG_WARNING, sprintf('Notice %d author profile id %d does not exist', $origNotice->id, $origNotice->profile_id)); - } catch (NoParentNoticeException $e) { - // This notice is not in reply to anything - } catch (Exception $e) { - common_log(LOG_WARNING, __METHOD__ . ' got exception ' . get_class($e) . ' : ' . $e->getMessage()); + $repliedTo = Profile::getByID($id); + $origMentions[$repliedTo->nickname] = $repliedTo; + } catch (NoResultException $e) { + // continue foreach } } From 44dc00a58c4d0f3d78e7b6dc2cee13959e480aad Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 12:53:10 +0200 Subject: [PATCH 057/156] Non-replies cannot harvest parent notice nicknames A feature we use of parent notices is that if you use the same @user as the parent notice, the same @user will be notified, regardless if there might be @user@site.com as well as @user@example.com and you're subscribed to just one of them (or both, or none of them!). But this threw an exception since we tested this on new notice threads. --- lib/util.php | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/util.php b/lib/util.php index 4d69651089..4949231c52 100644 --- a/lib/util.php +++ b/lib/util.php @@ -710,18 +710,22 @@ function common_find_mentions($text, Notice $notice) // Is it a reply? - $origNotice = $notice->getParent(); - $origAuthor = $origNotice->getProfile(); + try { + $origNotice = $notice->getParent(); + $origAuthor = $origNotice->getProfile(); - $ids = $origNotice->getReplies(); + $ids = $origNotice->getReplies(); - foreach ($ids as $id) { - try { - $repliedTo = Profile::getByID($id); - $origMentions[$repliedTo->nickname] = $repliedTo; - } catch (NoResultException $e) { - // continue foreach + foreach ($ids as $id) { + try { + $repliedTo = Profile::getByID($id); + $origMentions[$repliedTo->getNickname()] = $repliedTo; + } catch (NoResultException $e) { + // continue foreach + } } + } catch (NoParentNoticeException $e) { + // It wasn't a reply to anything, so we can't harvest nickname-relations. } $matches = common_find_mentions_raw($text); From 94d54ebc29ea9f7dddc9963e47a7cb68fe053048 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 18:45:59 +0200 Subject: [PATCH 058/156] Function declarations to match parent class --- lib/apignusocialoauthdatastore.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/apignusocialoauthdatastore.php b/lib/apignusocialoauthdatastore.php index da412df8d2..6f4403dec7 100644 --- a/lib/apignusocialoauthdatastore.php +++ b/lib/apignusocialoauthdatastore.php @@ -26,20 +26,20 @@ require_once 'OAuth.php'; */ class ApiGNUsocialOAuthDataStore extends OAuthDataStore { - function lookup_consumer($consumerKey) + function lookup_consumer($consumer_key) { - $con = Consumer::getKV('consumer_key', $consumerKey); + $con = Consumer::getKV('consumer_key', $consumer_key); if (!$con instanceof Consumer) { // Create an anon consumer and anon application if one // doesn't exist already - if ($consumerKey == 'anonymous') { + if ($consumer_key == 'anonymous') { common_debug("API OAuth - creating anonymous consumer"); $con = new Consumer(); - $con->consumer_key = $consumerKey; - $con->consumer_secret = $consumerKey; + $con->consumer_key = $consumer_key; + $con->consumer_secret = $consumer_key; $con->created = common_sql_now(); $result = $con->insert(); @@ -388,7 +388,7 @@ class ApiGNUsocialOAuthDataStore extends OAuthDataStore * * @return OAuthToken $token a new unauthorized OAuth request token */ - function new_request_token($consumer, $callback) + function new_request_token($consumer, $callback = null) { $t = new Token(); $t->consumer_key = $consumer->key; @@ -473,13 +473,13 @@ class ApiGNUsocialOAuthDataStore extends OAuthDataStore * @param type $token_key * @return OAuthToken */ - function lookup_token($consumer, $token_type, $token_key) + function lookup_token($consumer, $token_type, $token) { $t = new Token(); if (!is_null($consumer)) { $t->consumer_key = $consumer->key; } - $t->tok = $token_key; + $t->tok = $token; $t->type = ($token_type == 'access') ? 1 : 0; if ($t->find(true)) { return new OAuthToken($t->tok, $t->secret); From 673bef2fdae9db750f4daf5bfe7d639eb2ec2acc Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 18:52:43 +0200 Subject: [PATCH 059/156] OAuth widgets separated into their own files --- actions/oauthappssettings.php | 6 +- actions/oauthconnectionssettings.php | 6 +- lib/applicationlist.php | 145 +---------------------- lib/connectedappslist.php | 167 +++++++++++++++++++++++++++ lib/framework.php | 1 + 5 files changed, 171 insertions(+), 154 deletions(-) create mode 100644 lib/connectedappslist.php diff --git a/actions/oauthappssettings.php b/actions/oauthappssettings.php index 29e6d56073..e9b6280feb 100644 --- a/actions/oauthappssettings.php +++ b/actions/oauthappssettings.php @@ -27,11 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/applicationlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Show a user's registered OAuth applications diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index 9aa3ad434f..a3ba7eda39 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -27,11 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/applicationlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } /** * Show connected OAuth applications diff --git a/lib/applicationlist.php b/lib/applicationlist.php index d0c9256df7..b2cc572e3e 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -27,13 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/widget.php'; - -define('APPS_PER_PAGE', 20); +if (!defined('GNUSOCIAL')) { exit(1); } /** * Widget to show a list of OAuth applications @@ -119,140 +113,3 @@ class ApplicationList extends Widget return; } } - -/** - * Widget to show a list of connected OAuth clients - * - * @category Application - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ -class ConnectedAppsList extends Widget -{ - /** Current connected application query */ - var $connection = null; - - /** Owner of this list */ - var $owner = null; - - /** Action object using us. */ - var $action = null; - - function __construct($connection, $owner=null, $action=null) - { - parent::__construct($action); - - common_debug("ConnectedAppsList constructor"); - - $this->connection = $connection; - $this->owner = $owner; - $this->action = $action; - } - - /* Override this in subclasses. */ - function showOwnerControls() - { - return; - } - - function show() - { - $this->out->elementStart('ul', 'applications'); - - $cnt = 0; - - while ($this->connection->fetch()) { - $cnt++; - if($cnt > APPS_PER_PAGE) { - break; - } - $this->showConnection(); - } - - $this->out->elementEnd('ul'); - - return $cnt; - } - - function showConnection() - { - $app = Oauth_application::getKV('id', $this->connection->application_id); - - $this->out->elementStart('li', array('class' => 'application h-entry', - 'id' => 'oauthclient-' . $app->id)); - - $this->out->elementStart('a', array('href' => $app->source_url, - 'class' => 'h-card p-name')); - - if (!empty($app->icon)) { - $this->out->element('img', array('src' => $app->icon, - 'class' => 'avatar u-photo')); - } - if ($app->name != 'anonymous') { - $this->out->text($app->name); - } else { - // TRANS: Name for an anonymous application in application list. - $this->out->element('span', 'p-name', _('Unknown application')); - } - $this->out->elementEnd('a'); - - if ($app->name != 'anonymous') { - // @todo FIXME: i18n trouble. - // TRANS: Message has a leading space and a trailing space. Used in application list. - // TRANS: Before this message the application name is put, behind it the organisation that manages it. - $this->out->raw(_(' by ')); - - $this->out->element('a', array('href' => $app->homepage, - 'class' => 'h-card'), - $app->organization); - } - - // TRANS: Application access type - $readWriteText = _('read-write'); - // TRANS: Application access type - $readOnlyText = _('read-only'); - - $access = ($this->connection->access_type & Oauth_application::$writeAccess) - ? $readWriteText : $readOnlyText; - $modifiedDate = common_date_string($this->connection->modified); - // TRANS: Used in application list. %1$s is a modified date, %2$s is access type ("read-write" or "read-only") - $txt = sprintf(_('Approved %1$s - "%2$s" access.'), $modifiedDate, $access); - - // @todo FIXME: i18n trouble. - $this->out->raw(" - $txt"); - if (!empty($app->description)) { - $this->out->element( - 'p', array('class' => 'application_description'), - $app->description - ); - } - $this->out->element( - 'p', array( - 'class' => 'access_token'), - // TRANS: Access token in the application list. - // TRANS: %s are the first 7 characters of the access token. - sprintf(_('Access token starting with: %s'), substr($this->connection->token, 0, 7)) - ); - - $this->out->elementStart( - 'form', - array( - 'id' => 'form_revoke_app', - 'class' => 'form_revoke_app', - 'method' => 'POST', - 'action' => common_local_url('oauthconnectionssettings') - ) - ); - $this->out->elementStart('fieldset'); - $this->out->hidden('oauth_token', $this->connection->token); - $this->out->hidden('token', common_session_token()); - // TRANS: Button label in application list to revoke access to user data. - $this->out->submit('revoke', _m('BUTTON','Revoke')); - $this->out->elementEnd('fieldset'); - $this->out->elementEnd('form'); - - $this->out->elementEnd('li'); - } -} diff --git a/lib/connectedappslist.php b/lib/connectedappslist.php new file mode 100644 index 0000000000..7e5eb7482c --- /dev/null +++ b/lib/connectedappslist.php @@ -0,0 +1,167 @@ +. + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @copyright 2008-2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Widget to show a list of connected OAuth clients + * + * @category Application + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ +class ConnectedAppsList extends Widget +{ + /** Current connected application query */ + var $connection = null; + + /** Owner of this list */ + var $owner = null; + + /** Action object using us. */ + var $action = null; + + function __construct($connection, $owner=null, $action=null) + { + parent::__construct($action); + + common_debug("ConnectedAppsList constructor"); + + $this->connection = $connection; + $this->owner = $owner; + $this->action = $action; + } + + /* Override this in subclasses. */ + function showOwnerControls() + { + return; + } + + function show() + { + $this->out->elementStart('ul', 'applications'); + + $cnt = 0; + + while ($this->connection->fetch()) { + $cnt++; + if($cnt > APPS_PER_PAGE) { + break; + } + $this->showConnection(); + } + + $this->out->elementEnd('ul'); + + return $cnt; + } + + function showConnection() + { + $app = Oauth_application::getKV('id', $this->connection->application_id); + + $this->out->elementStart('li', array('class' => 'application h-entry', + 'id' => 'oauthclient-' . $app->id)); + + $this->out->elementStart('a', array('href' => $app->source_url, + 'class' => 'h-card p-name')); + + if (!empty($app->icon)) { + $this->out->element('img', array('src' => $app->icon, + 'class' => 'avatar u-photo')); + } + if ($app->name != 'anonymous') { + $this->out->text($app->name); + } else { + // TRANS: Name for an anonymous application in application list. + $this->out->element('span', 'p-name', _('Unknown application')); + } + $this->out->elementEnd('a'); + + if ($app->name != 'anonymous') { + // @todo FIXME: i18n trouble. + // TRANS: Message has a leading space and a trailing space. Used in application list. + // TRANS: Before this message the application name is put, behind it the organisation that manages it. + $this->out->raw(_(' by ')); + + $this->out->element('a', array('href' => $app->homepage, + 'class' => 'h-card'), + $app->organization); + } + + // TRANS: Application access type + $readWriteText = _('read-write'); + // TRANS: Application access type + $readOnlyText = _('read-only'); + + $access = ($this->connection->access_type & Oauth_application::$writeAccess) + ? $readWriteText : $readOnlyText; + $modifiedDate = common_date_string($this->connection->modified); + // TRANS: Used in application list. %1$s is a modified date, %2$s is access type ("read-write" or "read-only") + $txt = sprintf(_('Approved %1$s - "%2$s" access.'), $modifiedDate, $access); + + // @todo FIXME: i18n trouble. + $this->out->raw(" - $txt"); + if (!empty($app->description)) { + $this->out->element( + 'p', array('class' => 'application_description'), + $app->description + ); + } + $this->out->element( + 'p', array( + 'class' => 'access_token'), + // TRANS: Access token in the application list. + // TRANS: %s are the first 7 characters of the access token. + sprintf(_('Access token starting with: %s'), substr($this->connection->token, 0, 7)) + ); + + $this->out->elementStart( + 'form', + array( + 'id' => 'form_revoke_app', + 'class' => 'form_revoke_app', + 'method' => 'POST', + 'action' => common_local_url('oauthconnectionssettings') + ) + ); + $this->out->elementStart('fieldset'); + $this->out->hidden('oauth_token', $this->connection->token); + $this->out->hidden('token', common_session_token()); + // TRANS: Button label in application list to revoke access to user data. + $this->out->submit('revoke', _m('BUTTON','Revoke')); + $this->out->elementEnd('fieldset'); + $this->out->elementEnd('form'); + + $this->out->elementEnd('li'); + } +} diff --git a/lib/framework.php b/lib/framework.php index fec265fd1e..1834c3e786 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -37,6 +37,7 @@ define('NOTICES_PER_PAGE', 20); define('PROFILES_PER_PAGE', 20); define('MESSAGES_PER_PAGE', 20); define('GROUPS_PER_PAGE', 20); +define('APPS_PER_PAGE', 20); define('GROUPS_PER_MINILIST', 8); define('PROFILES_PER_MINILIST', 8); From 2d44400cfc720e212f506e86c29eec35d42a985d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 19:03:53 +0200 Subject: [PATCH 060/156] SettingsAction now extends FormAction (and thus ManagedAction) --- lib/settingsaction.php | 111 +---------------------------------------- 1 file changed, 2 insertions(+), 109 deletions(-) diff --git a/lib/settingsaction.php b/lib/settingsaction.php index fd4885c830..a98d002ca4 100644 --- a/lib/settingsaction.php +++ b/lib/settingsaction.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Base class for settings group of actions @@ -43,113 +41,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @see Widget */ -class SettingsAction extends Action +class SettingsAction extends FormAction { - /** - * A message for the user. - */ - - var $msg = null; - - /** - * Whether the message is a good one or a bad one. - */ - - var $success = false; - - /** - * Handle input and output a page - * - * @param array $args $_REQUEST arguments - * - * @return void - */ - - function handle($args) - { - parent::handle($args); - if (!common_logged_in()) { - // TRANS: Error message displayed when trying to perform an action that requires a logged in user. - $this->clientError(_('Not logged in.')); - } else if (!common_is_real_login()) { - // Cookie theft means that automatic logins can't - // change important settings or see private info, and - // _all_ our settings are important - common_set_returnto($this->selfUrl()); - $user = common_current_user(); - if (Event::handle('RedirectToLogin', array($this, $user))) { - common_redirect(common_local_url('login'), 303); - } - } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $this->handlePost(); - } else { - $this->showForm(); - } - } - - /** - * Handle a POST request - * - * @return boolean success flag - */ - - function handlePost() - { - return false; - } - - /** - * show the settings form - * - * @param string $msg an extra message for the user - * @param string $success good message or bad message? - * - * @return void - */ - - function showForm($msg=null, $success=false) - { - $this->msg = $msg; - $this->success = $success; - - $this->showPage(); - } - - /** - * show human-readable instructions for the page - * - * @return void - */ - - function showPageNotice() - { - if ($this->msg) { - $this->element('div', ($this->success) ? 'success' : 'error', - $this->msg); - } else { - $inst = $this->getInstructions(); - $output = common_markup_to_html($inst); - - $this->elementStart('div', 'instructions'); - $this->raw($output); - $this->elementEnd('div'); - } - } - - /** - * instructions recipe for sub-classes - * - * Subclasses should override this to return readable instructions. They'll - * be processed by common_markup_to_html(). - * - * @return string instructions text - */ - - function getInstructions() - { - return ''; - } - /** * Show the local navigation menu * From fd2efbc6f81aba67ff3776712af65b1a8fa73d06 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 19:21:12 +0200 Subject: [PATCH 061/156] AvatarSettings more aligned to FormAction TODO: Make classes called AvatarCropForm and AvatarUploadForm --- actions/avatarsettings.php | 108 ++++++++++--------------------------- lib/imagefile.php | 2 + 2 files changed, 31 insertions(+), 79 deletions(-) diff --git a/actions/avatarsettings.php b/actions/avatarsettings.php index 1f31cbdafe..4b618eb9be 100644 --- a/actions/avatarsettings.php +++ b/actions/avatarsettings.php @@ -76,11 +76,11 @@ class AvatarsettingsAction extends SettingsAction /** * Content area of the page * - * Shows a form for uploading an avatar. + * Shows a form for uploading an avatar. Currently overrides FormAction's showContent + * since we haven't made classes out of AvatarCropForm and AvatarUploadForm. * * @return void */ - function showContent() { if ($this->mode == 'crop') { @@ -243,52 +243,19 @@ class AvatarsettingsAction extends SettingsAction $this->elementEnd('form'); } - /** - * Handle a post - * - * We mux on the button name to figure out what the user actually wanted. - * - * @return void - */ - function handlePost() + protected function doPost() { - // Workaround for PHP returning empty $_POST and $_FILES when POST - // length > post_max_size in php.ini - - if (empty($_FILES) - && empty($_POST) - && ($_SERVER['CONTENT_LENGTH'] > 0) - ) { - // TRANS: Client error displayed when the number of bytes in a POST request exceeds a limit. - // TRANS: %s is the number of bytes of the CONTENT_LENGTH. - $msg = _m('The server was unable to handle that much POST data (%s byte) due to its current configuration.', - 'The server was unable to handle that much POST data (%s bytes) due to its current configuration.', - intval($_SERVER['CONTENT_LENGTH'])); - $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH'])); - return; - } - - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if (Event::handle('StartAvatarSaveForm', array($this))) { - if ($this->arg('upload')) { - $this->uploadAvatar(); - } else if ($this->arg('crop')) { - $this->cropAvatar(); - } else if ($this->arg('delete')) { - $this->deleteAvatar(); - } else { - // TRANS: Unexpected validation error on avatar upload form. - $this->showForm(_('Unexpected form submission.')); - } + if ($this->trimmed('upload')) { + return $this->uploadAvatar(); + } else if ($this->trimmed('crop')) { + return $this->cropAvatar(); + } else if ($this->trimmed('delete')) { + return $this->deleteAvatar(); + } else { + // TRANS: Unexpected validation error on avatar upload form. + throw new ClientException(_('Unexpected form submission.')); + } Event::handle('EndAvatarSaveForm', array($this)); } } @@ -303,21 +270,12 @@ class AvatarsettingsAction extends SettingsAction */ function uploadAvatar() { - try { - $imagefile = ImageFile::fromUpload('avatarfile'); - } catch (Exception $e) { - $this->showForm($e->getMessage()); - return; - } - if ($imagefile === null) { - // TRANS: Validation error on avatar upload form when no file was uploaded. - $this->showForm(_('No file uploaded.')); - return; - } + // ImageFile throws exception if something goes wrong, which we'll + // pick up and show as an error message above the form. + $imagefile = ImageFile::fromUpload('avatarfile'); - $cur = common_current_user(); $type = $imagefile->preferredType(); - $filename = Avatar::filename($cur->id, + $filename = Avatar::filename($this->scoped->getID(), image_type_to_extension($type), null, 'tmp'.common_timestamp()); @@ -338,8 +296,7 @@ class AvatarsettingsAction extends SettingsAction $this->mode = 'crop'; // TRANS: Avatar upload form instruction after uploading a file. - $this->showForm(_('Pick a square area of the image to be your avatar.'), - true); + return _('Pick a square area of the image to be your avatar.'); } /** @@ -351,13 +308,12 @@ class AvatarsettingsAction extends SettingsAction { $filedata = $_SESSION['FILEDATA']; - if (!$filedata) { + if (empty($filedata)) { // TRANS: Server error displayed if an avatar upload went wrong somehow server side. - $this->serverError(_('Lost our file data.')); + throw new ServerException(_('Lost our file data.')); } - $file_d = ($filedata['width'] > $filedata['height']) - ? $filedata['height'] : $filedata['width']; + $file_d = min($filedata['width'], $filedata['height']); $dest_x = $this->arg('avatar_crop_x') ? $this->arg('avatar_crop_x'):0; $dest_y = $this->arg('avatar_crop_y') ? $this->arg('avatar_crop_y'):0; @@ -369,11 +325,8 @@ class AvatarsettingsAction extends SettingsAction 'x' => $dest_x, 'y' => $dest_y, 'w' => $dest_w, 'h' => $dest_h); - $user = common_current_user(); - $profile = $user->getProfile(); - $imagefile = new ImageFile(null, $filedata['filepath']); - $filename = Avatar::filename($profile->getID(), image_type_to_extension($imagefile->preferredType()), + $filename = Avatar::filename($this->scoped->getID(), image_type_to_extension($imagefile->preferredType()), $size, common_timestamp()); try { $imagefile->resizeTo(Avatar::path($filename), $box); @@ -385,16 +338,16 @@ class AvatarsettingsAction extends SettingsAction } } - if ($profile->setOriginal($filename)) { + if ($this->scoped->setOriginal($filename)) { @unlink($filedata['filepath']); unset($_SESSION['FILEDATA']); $this->mode = 'upload'; // TRANS: Success message for having updated a user avatar. - $this->showForm(_('Avatar updated.'), true); - } else { - // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason. - $this->showForm(_('Failed updating avatar.')); + return _('Avatar updated.'); } + + // TRANS: Error displayed on the avatar upload page if the avatar could not be updated for an unknown reason. + throw new ServerException(_('Failed updating avatar.')); } /** @@ -404,13 +357,10 @@ class AvatarsettingsAction extends SettingsAction */ function deleteAvatar() { - $user = common_current_user(); - $profile = $user->getProfile(); - - Avatar::deleteFromProfile($profile); + Avatar::deleteFromProfile($this->scoped); // TRANS: Success message for deleting a user avatar. - $this->showForm(_('Avatar deleted.'), true); + return _('Avatar deleted.'); } /** diff --git a/lib/imagefile.php b/lib/imagefile.php index 2d1a3af02e..68cfea48e7 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -189,6 +189,8 @@ class ImageFile case UPLOAD_ERR_NO_FILE: // No file; probably just a non-AJAX submission. + throw new ClientException(_('No file uploaded.')); + default: common_log(LOG_ERR, __METHOD__ . ": Unknown upload error " . $_FILES[$param]['error']); // TRANS: Exception thrown when uploading an image fails for an unknown reason. From 647171e089d49eee770c77c7d1a5b9bbbdcdd55a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 19:42:37 +0200 Subject: [PATCH 062/156] EmailSettingsAction adapted to FormAction TODO: EmailSettingsForm as a separate class would be good! --- actions/emailsettings.php | 173 ++++++++++++++------------------------ 1 file changed, 63 insertions(+), 110 deletions(-) diff --git a/actions/emailsettings.php b/actions/emailsettings.php index cffc5da247..c02f1cdfad 100644 --- a/actions/emailsettings.php +++ b/actions/emailsettings.php @@ -28,11 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - - +if (!defined('GNUSOCIAL')) { exit(1); } /** * Settings for email @@ -112,8 +108,8 @@ class EmailsettingsAction extends SettingsAction // TRANS: Button label to remove a confirmed e-mail address. $this->submit('remove', _m('BUTTON','Remove')); } else { - $confirm = $this->getConfirmation(); - if ($confirm) { + try { + $confirm = $this->getConfirmation(); $this->element('p', array('id' => 'form_unconfirmed'), $confirm->address); $this->element('p', array('class' => 'form_note'), // TRANS: Form note in e-mail settings form. @@ -123,12 +119,12 @@ class EmailsettingsAction extends SettingsAction $this->hidden('email', $confirm->address); // TRANS: Button label to cancel an e-mail address confirmation procedure. $this->submit('cancel', _m('BUTTON','Cancel')); - } else { + } catch (NoResultException $e) { $this->elementStart('ul', 'form_data'); $this->elementStart('li'); // TRANS: Field label for e-mail address input in e-mail settings form. $this->input('email', _('Email address'), - ($this->arg('email')) ? $this->arg('email') : null, + $this->trimmed('email') ?: null, // TRANS: Instructions for e-mail address input form. Do not translate // TRANS: "example.org". It is one of the domain names reserved for // TRANS: use in examples by http://www.rfc-editor.org/rfc/rfc2606.txt. @@ -248,56 +244,36 @@ class EmailsettingsAction extends SettingsAction */ function getConfirmation() { - $user = common_current_user(); - $confirm = new Confirm_address(); - $confirm->user_id = $user->id; + $confirm->user_id = $this->scoped->getID(); $confirm->address_type = 'email'; if ($confirm->find(true)) { return $confirm; - } else { - return null; } + + throw new NoResultException($confirm); } - /** - * Handle posts - * - * Since there are a lot of different options on the page, we - * figure out what we're supposed to do based on which button was - * pushed - * - * @return void - */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->show_form(_('There was a problem with your session token. '. - 'Try again, please.')); - return; + if ($this->arg('save')) { + return $this->savePreferences(); + } else if ($this->arg('add')) { + return $this->addAddress(); + } else if ($this->arg('cancel')) { + return $this->cancelConfirmation(); + } else if ($this->arg('remove')) { + return $this->removeAddress(); + } else if ($this->arg('removeincoming')) { + return $this->removeIncoming(); + } else if ($this->arg('newincoming')) { + return $this->newIncoming(); } - if ($this->arg('save')) { - $this->savePreferences(); - } else if ($this->arg('add')) { - $this->addAddress(); - } else if ($this->arg('cancel')) { - $this->cancelConfirmation(); - } else if ($this->arg('remove')) { - $this->removeAddress(); - } else if ($this->arg('removeincoming')) { - $this->removeIncoming(); - } else if ($this->arg('newincoming')) { - $this->newIncoming(); - } else { - // TRANS: Message given submitting a form with an unknown action in e-mail settings. - $this->showForm(_('Unexpected form submission.')); - } + // TRANS: Message given submitting a form with an unknown action in e-mail settings. + throw new ClientException(_('Unexpected form submission.')); } /** @@ -307,8 +283,6 @@ class EmailsettingsAction extends SettingsAction */ function savePreferences() { - $user = $this->scoped->getUser(); - if (Event::handle('StartEmailSaveForm', array($this, $this->scoped))) { $emailnotifysub = $this->booleanintstring('emailnotifysub'); $emailnotifymsg = $this->booleanintstring('emailnotifymsg'); @@ -316,8 +290,8 @@ class EmailsettingsAction extends SettingsAction $emailnotifyattn = $this->booleanintstring('emailnotifyattn'); $emailpost = $this->booleanintstring('emailpost'); + $user = $this->scoped->getUser(); $user->query('BEGIN'); - $original = clone($user); $user->emailnotifysub = $emailnotifysub; @@ -332,16 +306,15 @@ class EmailsettingsAction extends SettingsAction 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.')); + throw new ServerException(_('Could not update user.')); } $user->query('COMMIT'); Event::handle('EndEmailSaveForm', array($this, $this->scoped)); - - // TRANS: Confirmation message for successful e-mail preferences save. - $this->showForm(_('Email preferences saved.'), true); } + // TRANS: Confirmation message for successful e-mail preferences save. + return _('Email preferences saved.'); } /** @@ -351,38 +324,32 @@ class EmailsettingsAction extends SettingsAction */ function addAddress() { - $user = common_current_user(); + $user = $this->scoped->getUser(); $email = $this->trimmed('email'); // Some validation - if (!$email) { + if (empty($email)) { // TRANS: Message given saving e-mail address without having provided one. - $this->showForm(_('No email address.')); - return; + throw new ClientException(_('No email address.')); } $email = common_canonical_email($email); - if (!$email) { + if (empty($email)) { // TRANS: Message given saving e-mail address that cannot be normalised. - $this->showForm(_('Cannot normalize that email address.')); - return; + throw new ClientException(_('Cannot normalize that email address.')); } if (!Validate::email($email, common_config('email', 'check_domain'))) { // TRANS: Message given saving e-mail address that not valid. - $this->showForm(_('Not a valid email address.')); - return; + throw new ClientException(_('Not a valid email address.')); } else if ($user->email == $email) { // TRANS: Message given saving e-mail address that is already set. - $this->showForm(_('That is already your email address.')); - return; + throw new ClientException(_('That is already your email address.')); } else if ($this->emailExists($email)) { // TRANS: Message given saving e-mail address that is already set for another user. - $this->showForm(_('That email address already belongs '. - 'to another user.')); - return; + throw new ClientException(_('That email address already belongs to another user.')); } if (Event::handle('StartAddEmailAddress', array($user, $email))) { @@ -391,7 +358,7 @@ class EmailsettingsAction extends SettingsAction $confirm->address = $email; $confirm->address_type = 'email'; - $confirm->user_id = $user->id; + $confirm->user_id = $user->getID(); $confirm->code = common_confirmation_code(64); $result = $confirm->insert(); @@ -399,21 +366,19 @@ class EmailsettingsAction extends SettingsAction if ($result === false) { common_log_db_error($confirm, 'INSERT', __FILE__); // TRANS: Server error thrown on database error adding e-mail confirmation code. - $this->serverError(_('Could not insert confirmation code.')); + throw new ServerException(_('Could not insert confirmation code.')); } - common_debug('Sending confirmation address for user '.$user->id.' to email '.$email); - mail_confirm_address($user, $confirm->code, $user->nickname, $email); + common_debug('Sending confirmation address for user '.$user->getID().' to email '.$email); + mail_confirm_address($user, $confirm->code, $user->getNickname(), $email); Event::handle('EndAddEmailAddress', array($user, $email)); } // TRANS: Message given saving valid e-mail address that is to be confirmed. - $msg = _('A confirmation code was sent to the email address you added. '. + return _('A confirmation code was sent to the email address you added. '. 'Check your inbox (and spam box!) for the code and instructions '. 'on how to use it.'); - - $this->showForm($msg, true); } /** @@ -423,31 +388,29 @@ class EmailsettingsAction extends SettingsAction */ function cancelConfirmation() { - $email = $this->arg('email'); + $email = $this->trimmed('email'); - $confirm = $this->getConfirmation(); - - if (!$confirm) { + try { + $confirm = $this->getConfirmation(); + if ($confirm->address !== $email) { + // TRANS: Message given canceling e-mail address confirmation for the wrong e-mail address. + throw new ClientException(_('That is the wrong email address.')); + } + } catch (NoResultException $e) { // TRANS: Message given canceling e-mail address confirmation that is not pending. - $this->showForm(_('No pending confirmation to cancel.')); - return; - } - if ($confirm->address != $email) { - // TRANS: Message given canceling e-mail address confirmation for the wrong e-mail address. - $this->showForm(_('That is the wrong email address.')); - return; + throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); } $result = $confirm->delete(); - if (!$result) { + if ($result === false) { common_log_db_error($confirm, 'DELETE', __FILE__); // TRANS: Server error thrown on database error canceling e-mail address confirmation. - $this->serverError(_('Could not delete email confirmation.')); + throw new ServerException(_('Could not delete email confirmation.')); } // TRANS: Message given after successfully canceling e-mail address confirmation. - $this->showForm(_('Email confirmation cancelled.'), true); + return _('Email confirmation cancelled.'); } /** @@ -459,26 +422,22 @@ class EmailsettingsAction extends SettingsAction { $user = common_current_user(); - $email = $this->arg('email'); + $email = $this->trimmed('email'); // Maybe an old tab open...? - - if ($user->email != $email) { + if ($user->email !== $email) { // TRANS: Message given trying to remove an e-mail address that is not // TRANS: registered for the active user. - $this->showForm(_('That is not your email address.')); - return; + throw new ClientException(_('That is not your email address.')); } $original = clone($user); - $user->email = null; - // Throws exception on failure. Also performs it within a transaction. $user->updateWithKeys($original); // TRANS: Message given after successfully removing a registered e-mail address. - $this->showForm(_('The email address was removed.'), true); + return _('The email address was removed.'); } /** @@ -490,22 +449,19 @@ class EmailsettingsAction extends SettingsAction { $user = common_current_user(); - if (!$user->incomingemail) { + if (empty($user->incomingemail)) { // TRANS: Form validation error displayed when trying to remove an incoming e-mail address while no address has been set. - $this->showForm(_('No incoming email address.')); - return; + throw new AlreadyFulfilledException(_('No incoming email address.')); } $orig = clone($user); - $user->incomingemail = null; $user->emailpost = 0; - // Throws exception on failure. Also performs it within a transaction. $user->updateWithKeys($orig); // TRANS: Message given after successfully removing an incoming e-mail address. - $this->showForm(_('Incoming email address removed.'), true); + return _('Incoming email address removed.'); } /** @@ -516,17 +472,14 @@ class EmailsettingsAction extends SettingsAction function newIncoming() { $user = common_current_user(); - $orig = clone($user); - $user->incomingemail = mail_new_incoming_address(); $user->emailpost = 1; - // Throws exception on failure. Also performs it within a transaction. $user->updateWithKeys($orig); // TRANS: Message given after successfully adding an incoming e-mail address. - $this->showForm(_('New incoming email address added.'), true); + return _('New incoming email address added.'); } /** @@ -545,10 +498,10 @@ class EmailsettingsAction extends SettingsAction $other = User::getKV('email', $email); - if (!$other) { + if (!$other instanceof User) { return false; - } else { - return $other->id != $user->id; } + + return $other->id != $user->id; } } From f1d9d8a6eded2d597d5a3157febc2de09c93d3a1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 21:18:50 +0200 Subject: [PATCH 063/156] ImSettings adapted to FormAction inheritance TODO: Get separate Form classes and move User_im_prefs to Profile_prefs --- actions/imsettings.php | 146 ++++++++++++++--------------------------- lib/implugin.php | 10 +-- 2 files changed, 55 insertions(+), 101 deletions(-) diff --git a/actions/imsettings.php b/actions/imsettings.php index feb5d32fc1..40bea10e68 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Settings for Jabber/XMPP integration @@ -118,8 +116,8 @@ class ImsettingsAction extends SettingsAction // TRANS: Button label to remove a confirmed IM address. $this->submit('remove', _m('BUTTON','Remove')); } else { - $confirm = $this->getConfirmation($transport); - if ($confirm) { + try { + $confirm = $this->getConfirmation($transport); $this->element('p', 'form_unconfirmed', $confirm->address); // TRANS: Form note in IM settings form. $this->element('p', 'form_note', @@ -134,7 +132,7 @@ class ImsettingsAction extends SettingsAction $this->hidden('screenname', $confirm->address); // TRANS: Button label to cancel an IM address confirmation procedure. $this->submit('cancel', _m('BUTTON','Cancel')); - } else { + } catch (NoResultException $e) { $this->elementStart('ul', 'form_data'); $this->elementStart('li'); // TRANS: Field label for IM address. @@ -209,57 +207,35 @@ class ImsettingsAction extends SettingsAction */ function getConfirmation($transport) { - $user = common_current_user(); - $confirm = new Confirm_address(); - $confirm->user_id = $user->id; + $confirm->user_id = $this->scoped->getID(); $confirm->address_type = $transport; if ($confirm->find(true)) { return $confirm; - } else { - return null; } + + throw new NoResultException($confirm); } - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if ($this->arg('save')) { - $this->savePreferences(); + return $this->savePreferences(); } else if ($this->arg('add')) { - $this->addAddress(); + return $this->addAddress(); } else if ($this->arg('cancel')) { - $this->cancelConfirmation(); + return $this->cancelConfirmation(); } else if ($this->arg('remove')) { - $this->removeAddress(); - } else { - // TRANS: Message given submitting a form with an unknown action in Instant Messaging settings. - $this->showForm(_('Unexpected form submission.')); + return $this->removeAddress(); } + // TRANS: Message given submitting a form with an unknown action in Instant Messaging settings. + throw new ClientException(_('Unexpected form submission.')); } /** - * Save user's Jabber preferences + * Save user's XMPP preferences * * These are the checkboxes at the bottom of the page. They're used to * set different settings @@ -268,11 +244,9 @@ class ImsettingsAction extends SettingsAction */ function savePreferences() { - $user = common_current_user(); - $user_im_prefs = new User_im_prefs(); $user_im_prefs->query('BEGIN'); - $user_im_prefs->user_id = $user->id; + $user_im_prefs->user_id = $this->scoped->getID(); if($user_im_prefs->find() && $user_im_prefs->fetch()) { $preferences = array('notify', 'updatefrompresence', 'replies'); @@ -287,15 +261,15 @@ class ImsettingsAction extends SettingsAction $result = $new->update($original); if ($result === false) { - common_log_db_error($user, 'UPDATE', __FILE__); + common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); // TRANS: Server error thrown on database error updating IM preferences. - $this->serverError(_('Could not update IM preferences.')); + throw new ServerException(_('Could not update IM preferences.')); } }while($user_im_prefs->fetch()); } $user_im_prefs->query('COMMIT'); // TRANS: Confirmation message for successful IM preferences save. - $this->showForm(_('Preferences saved.'), true); + return _('Preferences saved.'); } /** @@ -308,49 +282,42 @@ class ImsettingsAction extends SettingsAction */ function addAddress() { - $user = common_current_user(); - $screenname = $this->trimmed('screenname'); $transport = $this->trimmed('transport'); // Some validation - if (!$screenname) { + if (empty($screenname)) { // TRANS: Message given saving IM address without having provided one. - $this->showForm(_('No screenname.')); - return; + throw new ClientException(_('No screenname.')); } - if (!$transport) { + if (empty($transport)) { // TRANS: Form validation error when no transport is available setting an IM address. - $this->showForm(_('No transport.')); - return; + throw new ClientException(_('No transport.')); } Event::handle('NormalizeImScreenname', array($transport, &$screenname)); - if (!$screenname) { + if (empty($screenname)) { // TRANS: Message given saving IM address that cannot be normalised. - $this->showForm(_('Cannot normalize that screenname.')); - return; + throw new ClientException(_('Cannot normalize that screenname.')); } $valid = false; Event::handle('ValidateImScreenname', array($transport, $screenname, &$valid)); if (!$valid) { // TRANS: Message given saving IM address that not valid. - $this->showForm(_('Not a valid screenname.')); - return; + throw new ClientException(_('Not a valid screenname.')); } else if ($this->screennameExists($transport, $screenname)) { // TRANS: Message given saving IM address that is already set for another user. - $this->showForm(_('Screenname already belongs to another user.')); - return; + throw new ClientException(_('Screenname already belongs to another user.')); } $confirm = new Confirm_address(); $confirm->address = $screenname; $confirm->address_type = $transport; - $confirm->user_id = $user->id; + $confirm->user_id = $this->scoped->getID(); $confirm->code = common_confirmation_code(64); $confirm->sent = common_sql_now(); $confirm->claimed = common_sql_now(); @@ -363,13 +330,10 @@ class ImsettingsAction extends SettingsAction $this->serverError(_('Could not insert confirmation code.')); } - Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $user)); + Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $this->scoped)); // TRANS: Message given saving valid IM address that is to be confirmed. - $msg = _('A confirmation code was sent '. - 'to the IM address you added.'); - - $this->showForm($msg, true); + return _('A confirmation code was sent to the IM address you added.'); } /** @@ -384,29 +348,27 @@ class ImsettingsAction extends SettingsAction $screenname = $this->trimmed('screenname'); $transport = $this->trimmed('transport'); - $confirm = $this->getConfirmation($transport); - - if (!$confirm) { + try { + $confirm = $this->getConfirmation($transport); + if ($confirm->address != $screenname) { + // TRANS: Message given canceling IM address confirmation for the wrong IM address. + throw new ClientException(_('That is the wrong IM address.')); + } + } catch (NoResultException $e) { // TRANS: Message given canceling Instant Messaging address confirmation that is not pending. - $this->showForm(_('No pending confirmation to cancel.')); - return; - } - if ($confirm->address != $screenname) { - // TRANS: Message given canceling IM address confirmation for the wrong IM address. - $this->showForm(_('That is the wrong IM address.')); - return; + throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); } $result = $confirm->delete(); - if (!$result) { + if ($result === false) { common_log_db_error($confirm, 'DELETE', __FILE__); // TRANS: Server error thrown on database error canceling IM address confirmation. - $this->serverError(_('Could not delete confirmation.')); + throw new ServerException(_('Could not delete confirmation.')); } // TRANS: Message given after successfully canceling IM address confirmation. - $this->showForm(_('IM confirmation cancelled.'), true); + return _('IM confirmation cancelled.'); } /** @@ -418,34 +380,32 @@ class ImsettingsAction extends SettingsAction */ function removeAddress() { - $user = common_current_user(); - $screenname = $this->trimmed('screenname'); $transport = $this->trimmed('transport'); // Maybe an old tab open...? $user_im_prefs = new User_im_prefs(); - $user_im_prefs->user_id = $user->id; - if(! ($user_im_prefs->find() && $user_im_prefs->fetch())) { + $user_im_prefs->user_id = $this->scoped->getID(); + $user_im_prefs->transport = $transport; + if (!$user_im_prefs->find(true)) { // TRANS: Message given trying to remove an IM address that is not // TRANS: registered for the active user. - $this->showForm(_('That is not your screenname.')); - return; + throw new AlreadyFulfilledException(_('There were no preferences stored for this transport.')); } $result = $user_im_prefs->delete(); - if (!$result) { - common_log_db_error($user, 'UPDATE', __FILE__); + if ($result === false) { + common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); // TRANS: Server error thrown on database error removing a registered IM address. - $this->serverError(_('Could not update user IM preferences.')); + throw new ServerException(_('Could not update user IM preferences.')); } // XXX: unsubscribe to the old address // TRANS: Message given after successfully removing a registered Instant Messaging address. - $this->showForm(_('The IM address was removed.'), true); + return _('The IM address was removed.'); } /** @@ -461,15 +421,9 @@ class ImsettingsAction extends SettingsAction function screennameExists($transport, $screenname) { - $user = common_current_user(); - $user_im_prefs = new User_im_prefs(); $user_im_prefs->transport = $transport; $user_im_prefs->screenname = $screenname; - if($user_im_prefs->find() && $user_im_prefs->fetch()){ - return true; - }else{ - return false; - } + return $user_im_prefs->find(true) ? true : false; } } diff --git a/lib/implugin.php b/lib/implugin.php index 6395ecbdb7..742147dbbd 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -243,11 +243,11 @@ abstract class ImPlugin extends Plugin * * @param string $screenname screenname sending to * @param string $code the confirmation code - * @param User $user user sending to + * @param Profile $target For whom the code is valid for * * @return boolean success value */ - function sendConfirmationCode($screenname, $code, $user) + function sendConfirmationCode($screenname, $code, Profile $target) { // TRANS: Body text for confirmation code e-mail. // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename, @@ -258,7 +258,7 @@ abstract class ImPlugin extends Plugin ' . (If you cannot click it, copy-and-paste it into the ' . 'address bar of your browser). If that user is not you, ' . 'or if you did not request this confirmation, just ignore this message.'), - $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', null, array('code' => $code))); + $target->getNickname(), common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', null, array('code' => $code))); return $this->sendMessage($screenname, $body); } @@ -594,11 +594,11 @@ abstract class ImPlugin extends Plugin 'daemonScreenname' => $this->daemonScreenname()); } - function onSendImConfirmationCode($transport, $screenname, $code, $user) + function onSendImConfirmationCode($transport, $screenname, $code, Profile $target) { if($transport == $this->transport) { - $this->sendConfirmationCode($screenname, $code, $user); + $this->sendConfirmationCode($screenname, $code, $target); return false; } } From ba5a43f2f9c712cfb4d5dac4112e8cab25b4260c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 16 Jul 2015 23:58:04 +0200 Subject: [PATCH 064/156] If XMLOutputter $output arg is null, use php://output Since pushing a null value to the argument actually sets it to null and not the default fallback (previously $output='php://output'); --- lib/xmloutputter.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/xmloutputter.php b/lib/xmloutputter.php index 71d57e2d56..463f91be30 100644 --- a/lib/xmloutputter.php +++ b/lib/xmloutputter.php @@ -63,12 +63,15 @@ class XMLOutputter * * Initializes the wrapped XMLWriter. * - * @param string $output URL for outputting, defaults to stdout + * @param string $output URL for outputting, if null it defaults to stdout ('php://output') * @param boolean $indent Whether to indent output, default true */ - function __construct($output='php://output', $indent=null) + function __construct($output=null, $indent=null) { + if (is_null($output)) { + $output = 'php://output'; + } $this->xw = new XMLWriter(); $this->xw->openURI($output); if(is_null($indent)) { From a6e299a2fc83d7b4d0e82082f7622279f5d78aba Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 00:20:46 +0200 Subject: [PATCH 065/156] OAuth stuff adapted for FormAction TODO: Break OAuth out into a plugin. --- actions/newapplication.php | 7 ++-- actions/oauthappssettings.php | 51 ++++------------------------ actions/oauthconnectionssettings.php | 40 +++++++--------------- classes/Profile.php | 5 +++ lib/applicationlist.php | 12 ++----- lib/connectedappslist.php | 10 ++---- 6 files changed, 34 insertions(+), 91 deletions(-) diff --git a/actions/newapplication.php b/actions/newapplication.php index 37bede0d72..5032bb7495 100644 --- a/actions/newapplication.php +++ b/actions/newapplication.php @@ -41,7 +41,7 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class NewApplicationAction extends FormAction +class NewApplicationAction extends SettingsAction { function title() { @@ -54,6 +54,7 @@ class NewApplicationAction extends FormAction if ($this->arg('cancel')) { common_redirect(common_local_url('oauthappssettings'), 303); } elseif ($this->arg('save')) { + //trySave will never return, just throw exception or redirect $this->trySave(); } @@ -72,7 +73,7 @@ class NewApplicationAction extends FormAction return _('Use this form to register a new application.'); } - private function trySave() + protected function trySave() { $name = $this->trimmed('name'); $description = $this->trimmed('description'); @@ -137,7 +138,7 @@ class NewApplicationAction extends FormAction $app->query('BEGIN'); $app->name = $name; - $app->owner = $this->scoped->id; + $app->owner = $this->scoped->getID(); $app->description = $description; $app->source_url = $source_url; $app->organization = $organization; diff --git a/actions/oauthappssettings.php b/actions/oauthappssettings.php index e9b6280feb..43e9b33663 100644 --- a/actions/oauthappssettings.php +++ b/actions/oauthappssettings.php @@ -43,19 +43,11 @@ if (!defined('GNUSOCIAL')) { exit(1); } class OauthappssettingsAction extends SettingsAction { - var $page = 0; + protected $page = null; - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; - - if (!common_logged_in()) { - // TRANS: Message displayed to an anonymous user trying to view OAuth application list. - $this->clientError(_('You must be logged in to list your applications.')); - } - - return true; + $this->page = $this->int('page') ?: 1; } /** @@ -82,21 +74,13 @@ class OauthappssettingsAction extends SettingsAction return _('Applications you have registered'); } - /** - * Content area of the page - * - * @return void - */ - function showContent() { - $user = common_current_user(); - $offset = ($this->page - 1) * APPS_PER_PAGE; $limit = APPS_PER_PAGE + 1; $application = new Oauth_application(); - $application->owner = $user->id; + $application->owner = $this->scoped->getID(); $application->whereAdd("name != 'anonymous'"); $application->limit($offset, $limit); $application->orderBy('created DESC'); @@ -105,7 +89,7 @@ class OauthappssettingsAction extends SettingsAction $cnt = 0; if ($application) { - $al = new ApplicationList($application, $user, $this); + $al = new ApplicationList($application, $this->scoped, $this); $cnt = $al->show(); if (0 == $cnt) { $this->showEmptyListMessage(); @@ -131,34 +115,11 @@ class OauthappssettingsAction extends SettingsAction function showEmptyListMessage() { - // TRANS: Empty list message on page with OAuth applications. + // TRANS: Empty list message on page with OAuth applications. Markup allowed $message = sprintf(_('You have not registered any applications yet.')); $this->elementStart('div', 'guide'); $this->raw(common_markup_to_html($message)); $this->elementEnd('div'); } - - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - - function handlePost() - { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - } } diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php index a3ba7eda39..0c5a143443 100644 --- a/actions/oauthconnectionssettings.php +++ b/actions/oauthconnectionssettings.php @@ -42,15 +42,14 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class OauthconnectionssettingsAction extends SettingsAction { - var $page = null; - var $oauth_token = null; + var $page = null; - function prepare($args) + protected $oauth_token = null; + + protected function doPreparation() { - parent::prepare($args); $this->oauth_token = $this->arg('oauth_token'); - $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1; - return true; + $this->page = $this->int('page') ?: 1; } /** @@ -83,18 +82,15 @@ class OauthconnectionssettingsAction extends SettingsAction function showContent() { - $user = common_current_user(); - $profile = $user->getProfile(); - $offset = ($this->page - 1) * APPS_PER_PAGE; $limit = APPS_PER_PAGE + 1; - $connection = $user->getConnectedApps($offset, $limit); + $connection = $this->scoped->getConnectedApps($offset, $limit); $cnt = 0; if (!empty($connection)) { - $cal = new ConnectedAppsList($connection, $user, $this); + $cal = new ConnectedAppsList($connection, $this->scoped, $this); $cnt = $cal->show(); } @@ -107,7 +103,7 @@ class OauthconnectionssettingsAction extends SettingsAction $cnt > APPS_PER_PAGE, $this->page, 'connectionssettings', - array('nickname' => $user->nickname) + array('nickname' => $this->scoped->getNickname()) ); } @@ -121,24 +117,14 @@ class OauthconnectionssettingsAction extends SettingsAction * * @return void */ - function handlePost() + protected function doPost() { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if ($this->arg('revoke')) { - $this->revokeAccess($this->oauth_token); - } else { - // TRANS: Client error when submitting a form with unexpected information. - $this->clientError(_('Unexpected form submission.'), 401); + return $this->revokeAccess($this->oauth_token); } + + // TRANS: Client error when submitting a form with unexpected information. + throw new ClientException(_('Unexpected form submission.'), 401); } /** diff --git a/classes/Profile.php b/classes/Profile.php index f628965a74..384eaa0726 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -1623,4 +1623,9 @@ class Profile extends Managed_DataObject public function setPref($namespace, $topic, $data) { return Profile_prefs::setData($this, $namespace, $topic, $data); } + + public function getConnectedApps($offset=0, $limit=null) + { + return $this->getUser()->getConnectedApps($offset, $limit); + } } diff --git a/lib/applicationlist.php b/lib/applicationlist.php index b2cc572e3e..ab51a73096 100644 --- a/lib/applicationlist.php +++ b/lib/applicationlist.php @@ -46,16 +46,12 @@ class ApplicationList extends Widget /** Owner of this list */ var $owner = null; - /** Action object using us. */ - var $action = null; - - function __construct($application, $owner=null, $action=null) + function __construct($application, Profile $owner, Action $out=null) { - parent::__construct($action); + parent::__construct($out); $this->application = $application; $this->owner = $owner; - $this->action = $action; } function show() @@ -69,7 +65,7 @@ class ApplicationList extends Widget if($cnt > APPS_PER_PAGE) { break; } - $this->showapplication(); + $this->showApplication(); } $this->out->elementEnd('ul'); @@ -79,8 +75,6 @@ class ApplicationList extends Widget function showApplication() { - $user = common_current_user(); - $this->out->elementStart('li', array('class' => 'application h-entry', 'id' => 'oauthclient-' . $this->application->id)); diff --git a/lib/connectedappslist.php b/lib/connectedappslist.php index 7e5eb7482c..c2a27e75a8 100644 --- a/lib/connectedappslist.php +++ b/lib/connectedappslist.php @@ -46,18 +46,14 @@ class ConnectedAppsList extends Widget /** Owner of this list */ var $owner = null; - /** Action object using us. */ - var $action = null; - - function __construct($connection, $owner=null, $action=null) + function __construct($connection, Profile $owner, Action $out=null) { - parent::__construct($action); + parent::__construct($out); common_debug("ConnectedAppsList constructor"); $this->connection = $connection; - $this->owner = $owner; - $this->action = $action; + $this->owner = $owner; } /* Override this in subclasses. */ From 47ef917f62c445d29b2690d73c1a7ae991fcc4dd Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 01:18:28 +0200 Subject: [PATCH 066/156] oldschool settings adapted to FormAction TODO: Rename this "Layout", "Display" or something, since it might actually be interesting to have non-threaded views for some users. --- actions/oldschoolsettings.php | 45 ++++++++--------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/actions/oldschoolsettings.php b/actions/oldschoolsettings.php index 25ec13e481..5c07195478 100644 --- a/actions/oldschoolsettings.php +++ b/actions/oldschoolsettings.php @@ -28,11 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Old-school settings @@ -77,35 +73,23 @@ class OldschoolsettingsAction extends SettingsAction * @return boolean true */ - function prepare($argarray) + protected function doPreparation() { if (!common_config('oldschool', 'enabled')) { throw new ClientException("Old-school settings not enabled."); } - parent::prepare($argarray); - return true; } - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - - function handlePost() + function doPost() { - $user = common_current_user(); - - $osp = Old_school_prefs::getKV('user_id', $user->id); + $osp = Old_school_prefs::getKV('user_id', $this->scoped->getID()); $orig = null; if (!empty($osp)) { $orig = clone($osp); } else { $osp = new Old_school_prefs(); - $osp->user_id = $user->id; + $osp->user_id = $this->scoped->getID(); $osp->created = common_sql_now(); } @@ -113,34 +97,25 @@ class OldschoolsettingsAction extends SettingsAction $osp->stream_nicknames = $this->boolean('stream_nicknames'); $osp->modified = common_sql_now(); - if (!empty($orig)) { + if ($orig instanceof Old_school_prefs) { $osp->update($orig); } else { $osp->insert(); } // TRANS: Confirmation shown when user profile settings are saved. - $this->showForm(_('Settings saved.'), true); - - return; - } - - function showContent() - { - $user = common_current_user(); - $form = new OldSchoolForm($this, $user); - $form->show(); + return _('Settings saved.'); } } -class OldSchoolForm extends Form +class OldSchoolSettingsForm extends Form { var $user; - function __construct($out, $user) + function __construct(Action $out) { parent::__construct($out); - $this->user = $user; + $this->user = $out->getScoped()->getUser(); } /** From cfaaf3c13cd97b73c4a7033f7714e36e294e3d9b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 01:47:43 +0200 Subject: [PATCH 067/156] PasswordsettingsAction aligned with FormAction Also made some changes in the password "munging" function call common_munge_password to accept a profile instead of user ID (which was only there because stoneage StatusNet used the ID to generate a not-very-random salt, but nowadays we primarily use AuthCrypt plugin). --- EVENTS.txt | 4 +- actions/passwordsettings.php | 72 +++++-------------- actions/recoverpassword.php | 2 +- classes/Profile.php | 12 ++++ classes/User.php | 7 +- lib/authenticationplugin.php | 6 +- lib/util.php | 5 +- plugins/AuthCrypt/AuthCryptPlugin.php | 8 +-- .../actions/confirmfirstemail.php | 2 +- 9 files changed, 47 insertions(+), 71 deletions(-) diff --git a/EVENTS.txt b/EVENTS.txt index cfba97403b..2f91a305f0 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -615,12 +615,12 @@ EndCheckPassword: After checking a username/password pair - $authenticatedUser: User object if credentials match a user, else null. StartChangePassword: Before changing a password -- $user: user +- Profile $target: The profile of the User that is changing password - $oldpassword: the user's old password - $newpassword: the desired new password EndChangePassword: After changing a password -- $user: user +- Profile $target: The profile of the User that just changed its password StartHashPassword: Generate a hashed version of the password (like a salted crypt) - &$hashed: Hashed version of the password, later put in the database diff --git a/actions/passwordsettings.php b/actions/passwordsettings.php index db36b612a2..cfdb6c7817 100644 --- a/actions/passwordsettings.php +++ b/actions/passwordsettings.php @@ -28,11 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - - +if (!defined('STATUSNET')) { exit(1); } /** * Change password @@ -77,18 +73,8 @@ class PasswordsettingsAction extends SettingsAction $this->autofocus('oldpassword'); } - /** - * Content area of the page - * - * Shows a form for changing the password - * - * @return void - */ - function showContent() { - $user = common_current_user(); - $this->elementStart('form', array('method' => 'POST', 'id' => 'form_password', 'class' => 'form_settings', @@ -102,7 +88,7 @@ class PasswordsettingsAction extends SettingsAction $this->elementStart('ul', 'form_data'); // Users who logged in with OpenID won't have a pwd - if ($user->password) { + if ($this->scoped->hasPassword()) { $this->elementStart('li'); // TRANS: Field label on page where to change password. $this->password('oldpassword', _('Old password')); @@ -129,29 +115,8 @@ class PasswordsettingsAction extends SettingsAction $this->elementEnd('form'); } - /** - * Handle a post - * - * Validate input and save changes. Reload the form with a success - * or error message. - * - * @return void - */ - function handlePost() + protected function doPost() { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - - $user = common_current_user(); - assert(!is_null($user)); // should already be checked - // FIXME: scrub input $newpassword = $this->arg('newpassword'); @@ -161,49 +126,44 @@ class PasswordsettingsAction extends SettingsAction if (strlen($newpassword) < 6) { // TRANS: Form validation error on page where to change password. - $this->showForm(_('Password must be 6 or more characters.')); - return; + throw new ClientException(_('Password must be 6 or more characters.')); } else if (0 != strcmp($newpassword, $confirm)) { // TRANS: Form validation error on password change when password confirmation does not match. - $this->showForm(_('Passwords do not match.')); - return; + throw new ClientException(_('Passwords do not match.')); } - if ($user->password) { + $oldpassword = null; + if ($this->scoped->hasPassword()) { $oldpassword = $this->arg('oldpassword'); - if (!common_check_user($user->nickname, $oldpassword)) { + if (!common_check_user($this->scoped->getNickname(), $oldpassword)) { // TRANS: Form validation error on page where to change password. - $this->showForm(_('Incorrect old password.')); - return; + throw new ClientException(_('Incorrect old password.')); } - }else{ - $oldpassword = null; } - $success = false; - if(Event::handle('StartChangePassword', array($user, $oldpassword, $newpassword))){ + if (Event::handle('StartChangePassword', array($this->scoped, $oldpassword, $newpassword))) { //no handler changed the password, so change the password internally + $user = $this->scoped->getUser(); $original = clone($user); - $user->password = common_munge_password($newpassword, $user->id); + $user->password = common_munge_password($newpassword, $this->scoped); $val = $user->validate(); if ($val !== true) { // TRANS: Form validation error on page where to change password. - $this->showForm(_('Error saving user; invalid.')); - return; + throw new ServerException(_('Error saving user; invalid.')); } if (!$user->update($original)) { // TRANS: Server error displayed on page where to change password when password change // TRANS: could not be made because of a server error. - $this->serverError(_('Cannot save new password.')); + throw new ServerException(_('Cannot save new password.')); } - Event::handle('EndChangePassword', array($user)); + Event::handle('EndChangePassword', array($this->scoped)); } // TRANS: Form validation notice on page where to change password. - $this->showForm(_('Password saved.'), true); + return _('Password saved.'); } } diff --git a/actions/recoverpassword.php b/actions/recoverpassword.php index 060ba83510..1cbb73fd4a 100644 --- a/actions/recoverpassword.php +++ b/actions/recoverpassword.php @@ -325,7 +325,7 @@ class RecoverpasswordAction extends Action $original = clone($user); - $user->password = common_munge_password($newpassword, $user->id); + $user->password = common_munge_password($newpassword, $user->getProfile()); if (!$user->update($original)) { common_log_db_error($user, 'UPDATE', __FILE__); diff --git a/classes/Profile.php b/classes/Profile.php index 384eaa0726..5709a15d01 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -138,6 +138,18 @@ class Profile extends Managed_DataObject return true; } + // Returns false if the user has no password (which will always + // be the case for remote users). This can be the case for OpenID + // logins or other mechanisms which don't store a password hash. + public function hasPassword() + { + try { + return !empty($this->getUser()->hasPassword()); + } catch (NoSuchUserException $e) { + return false; + } + } + public function getObjectType() { // FIXME: More types... like peopletags and whatever diff --git a/classes/User.php b/classes/User.php index e1ce33be18..175d945401 100644 --- a/classes/User.php +++ b/classes/User.php @@ -299,7 +299,7 @@ class User extends Managed_DataObject } if (!empty($password)) { // may not have a password for OpenID users - $user->password = common_munge_password($password, $id); + $user->password = common_munge_password($password); } $result = $user->insert(); @@ -1015,6 +1015,11 @@ class User extends Managed_DataObject return $this->getProfile()->isPrivateStream(); } + public function hasPassword() + { + return !empty($this->password); + } + public function delPref($namespace, $topic) { return $this->getProfile()->delPref($namespace, $topic); diff --git a/lib/authenticationplugin.php b/lib/authenticationplugin.php index 66f11ca1a9..c33b0ef8a8 100644 --- a/lib/authenticationplugin.php +++ b/lib/authenticationplugin.php @@ -201,13 +201,13 @@ abstract class AuthenticationPlugin extends Plugin } } - function onStartChangePassword($user,$oldpassword,$newpassword) + function onStartChangePassword(Profile $target ,$oldpassword, $newpassword) { if($this->password_changeable){ $user_username = new User_username(); - $user_username->user_id=$user->id; + $user_username->user_id = $target->getID(); $user_username->provider_name=$this->provider_name; - if($user_username->find() && $user_username->fetch()){ + if ($user_username->find(true)) { $authenticated = $this->checkPassword($user_username->username, $oldpassword); if($authenticated){ $result = $this->changePassword($user_username->username,$oldpassword,$newpassword); diff --git a/lib/util.php b/lib/util.php index 4949231c52..1ff5b13b93 100644 --- a/lib/util.php +++ b/lib/util.php @@ -210,7 +210,7 @@ function common_language() /** * Salted, hashed passwords are stored in the DB. */ -function common_munge_password($password, $id, Profile $profile=null) +function common_munge_password($password, Profile $profile=null) { $hashed = null; @@ -245,8 +245,7 @@ function common_check_user($nickname, $password) } if ($user instanceof User && !empty($password)) { - if (0 == strcmp(common_munge_password($password, $user->id), - $user->password)) { + if (0 == strcmp(common_munge_password($password, $user->getProfile()), $user->password)) { //internal checking passed $authenticatedUser = $user; } diff --git a/plugins/AuthCrypt/AuthCryptPlugin.php b/plugins/AuthCrypt/AuthCryptPlugin.php index c669566772..540019f9c2 100644 --- a/plugins/AuthCrypt/AuthCryptPlugin.php +++ b/plugins/AuthCrypt/AuthCryptPlugin.php @@ -110,17 +110,17 @@ class AuthCryptPlugin extends AuthenticationPlugin * EVENTS */ - public function onStartChangePassword($user, $oldpassword, $newpassword) + public function onStartChangePassword(Profile $target, $oldpassword, $newpassword) { - if (!$this->checkPassword($user->nickname, $oldpassword)) { + if (!$this->checkPassword($target->getNickname(), $oldpassword)) { // if we ARE in overwrite mode, test password with common_check_user - if (!$this->overwrite || !common_check_user($user->nickname, $oldpassword)) { + if (!$this->overwrite || !common_check_user($target->getNickname(), $oldpassword)) { // either we're not in overwrite mode, or the password was incorrect return !$this->authoritative; } // oldpassword was apparently ok } - $changed = $this->changePassword($user->nickname, $oldpassword, $newpassword); + $changed = $this->changePassword($target->getNickname(), $oldpassword, $newpassword); return (!$changed && empty($this->authoritative)); } diff --git a/plugins/RequireValidatedEmail/actions/confirmfirstemail.php b/plugins/RequireValidatedEmail/actions/confirmfirstemail.php index cc4b646f6b..d0d1893739 100644 --- a/plugins/RequireValidatedEmail/actions/confirmfirstemail.php +++ b/plugins/RequireValidatedEmail/actions/confirmfirstemail.php @@ -157,7 +157,7 @@ class ConfirmfirstemailAction extends Action $orig = clone($this->user); - $this->user->password = common_munge_password($this->password, $this->user->id); + $this->user->password = common_munge_password($this->password, $this->user->getProfile()); $this->user->update($orig); From 53e820b46667c2f0e22ffa0a2f91e847a02f4cb8 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 11:22:25 +0200 Subject: [PATCH 068/156] Maximum character limit with utf8mb4 is 191 in varchar --- actions/profilesettings.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actions/profilesettings.php b/actions/profilesettings.php index 7d3143d4b1..3bdad42d9a 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -275,9 +275,9 @@ class ProfilesettingsAction extends SettingsAction // TRANS: Validation error in form for profile settings. $this->showForm(_('Homepage is not a valid URL.')); return; - } else if (!is_null($fullname) && mb_strlen($fullname) > 255) { + } else if (!is_null($fullname) && mb_strlen($fullname) > 191) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Full name is too long (maximum 255 characters).')); + $this->showForm(_('Full name is too long (maximum 191 characters).')); return; } else if (Profile::bioTooLong($bio)) { // TRANS: Validation error in form for profile settings. @@ -288,9 +288,9 @@ class ProfilesettingsAction extends SettingsAction Profile::maxBio()), Profile::maxBio())); return; - } else if (!is_null($location) && mb_strlen($location) > 255) { + } else if (!is_null($location) && mb_strlen($location) > 191) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Location is too long (maximum 255 characters).')); + $this->showForm(_('Location is too long (maximum 191 characters).')); return; } else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) { // TRANS: Validation error in form for profile settings. From 9f82da07f105792403807ab444de873bfcd7a45f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 12:09:24 +0200 Subject: [PATCH 069/156] ProfilesettingsAction and related stuff modernised --- actions/profilesettings.php | 137 +++++++++++++++--------------------- classes/Profile.php | 5 ++ classes/Profile_tag.php | 52 +++++++------- classes/User.php | 10 --- 4 files changed, 85 insertions(+), 119 deletions(-) diff --git a/actions/profilesettings.php b/actions/profilesettings.php index 3bdad42d9a..5804f21ca5 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -82,7 +82,6 @@ class ProfilesettingsAction extends SettingsAction */ function showContent() { - $profile = $this->scoped; $user = $this->scoped->getUser(); $this->elementStart('form', array('method' => 'post', @@ -100,7 +99,7 @@ class ProfilesettingsAction extends SettingsAction $this->elementStart('li'); // TRANS: Field label in form for profile settings. $this->input('nickname', _('Nickname'), - $this->arg('nickname') ?: $profile->nickname, + $this->trimmed('nickname') ?: $this->scoped->getNickname(), // 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" @@ -111,12 +110,12 @@ class ProfilesettingsAction extends SettingsAction $this->elementStart('li'); // TRANS: Field label in form for profile settings. $this->input('fullname', _('Full name'), - ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); + $this->trimmed('fullname') ?: $this->scoped->getFullname()); $this->elementEnd('li'); $this->elementStart('li'); // TRANS: Field label in form for profile settings. $this->input('homepage', _('Homepage'), - ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, + $this->trimmed('homepage') ?: $this->scoped->getHomepage(), // TRANS: Tooltip for field label in form for profile settings. _('URL of your homepage, blog, or profile on another site.')); $this->elementEnd('li'); @@ -137,13 +136,13 @@ class ProfilesettingsAction extends SettingsAction // TRANS: Text area label in form for profile settings where users can provide // TRANS: their biography. $this->textarea('bio', _('Bio'), - ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, + $this->trimmed('bio') ?: $this->scoped->getDescription(), $bioInstr); $this->elementEnd('li'); $this->elementStart('li'); // TRANS: Field label in form for profile settings. $this->input('location', _('Location'), - ($this->arg('location')) ? $this->arg('location') : $profile->location, + $this->trimmed('location') ?: $this->scoped->location, // TRANS: Tooltip for field label in form for profile settings. _('Where you are, like "City, State (or Region), Country".')); $this->elementEnd('li'); @@ -152,14 +151,14 @@ class ProfilesettingsAction extends SettingsAction // TRANS: Checkbox label in form for profile settings. $this->checkbox('sharelocation', _('Share my current location when posting notices'), ($this->arg('sharelocation')) ? - $this->arg('sharelocation') : $this->scoped->shareLocation()); + $this->boolean('sharelocation') : $this->scoped->shareLocation()); $this->elementEnd('li'); } Event::handle('EndProfileFormData', array($this)); $this->elementStart('li'); // TRANS: Field label in form for profile settings. $this->input('tags', _('Tags'), - ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), + $this->trimmed('tags') ?: implode(' ', Profile_tag::getSelfTagsArray($this->scoped)), // TRANS: Tooltip for field label in form for profile settings. _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated.')); $this->elementEnd('li'); @@ -228,17 +227,8 @@ class ProfilesettingsAction extends SettingsAction * * @return void */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Form validation error. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if (Event::handle('StartProfileSaveForm', array($this))) { // $nickname will only be set if this changenick value is true. @@ -246,15 +236,13 @@ class ProfilesettingsAction extends SettingsAction try { $nickname = Nickname::normalize($this->trimmed('nickname'), true); } catch (NicknameTakenException $e) { - // Abort only if the nickname is occupied by another local profile - if ($e->profile->id != $this->scoped->id) { - $this->showForm($e->getMessage()); - return; + // Abort only if the nickname is occupied by _another_ local user profile + if (!$this->scoped->sameAs($e->profile)) { + throw $e; } - $nickname = Nickname::normalize($this->trimmed('nickname')); // without in-use check this time - } catch (NicknameException $e) { - $this->showForm($e->getMessage()); - return; + // Since the variable wasn't set before the exception was thrown, let's run + // the normalize sequence again, but without in-use check this time. + $nickname = Nickname::normalize($this->trimmed('nickname')); } } @@ -273,33 +261,27 @@ class ProfilesettingsAction extends SettingsAction if (!is_null($homepage) && (strlen($homepage) > 0) && !common_valid_http_url($homepage)) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Homepage is not a valid URL.')); - return; + throw new ClientException(_('Homepage is not a valid URL.')); } else if (!is_null($fullname) && mb_strlen($fullname) > 191) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Full name is too long (maximum 191 characters).')); - return; + throw new ClientException(_('Full name is too long (maximum 191 characters).')); } else if (Profile::bioTooLong($bio)) { // TRANS: Validation error in form for profile settings. // TRANS: Plural form is used based on the maximum number of allowed // TRANS: characters for the biography (%d). - $this->showForm(sprintf(_m('Bio is too long (maximum %d character).', + throw new ClientException(sprintf(_m('Bio is too long (maximum %d character).', 'Bio is too long (maximum %d characters).', Profile::maxBio()), Profile::maxBio())); - return; } else if (!is_null($location) && mb_strlen($location) > 191) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Location is too long (maximum 191 characters).')); - return; + throw new ClientException(_('Location is too long (maximum 191 characters).')); } else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Timezone not selected.')); - return; + throw new ClientException(_('Timezone not selected.')); } else if (!is_null($language) && strlen($language) > 50) { // TRANS: Validation error in form for profile settings. - $this->showForm(_('Language is too long (maximum 50 characters).')); - return; + throw new ClientException(_('Language is too long (maximum 50 characters).')); } $tags = array(); @@ -315,15 +297,14 @@ class ProfilesettingsAction extends SettingsAction if (!common_valid_profile_tag($tag)) { // TRANS: Validation error in form for profile settings. // TRANS: %s is an invalid tag. - $this->showForm(sprintf(_('Invalid tag: "%s".'), $tag)); - return; + throw new ClientException(sprintf(_('Invalid tag: "%s".'), $tag)); } $tag_priv[$tag] = $private; } } - $user = common_current_user(); + $user = $this->scoped->getUser(); $user->query('BEGIN'); // $user->nickname is updated through Profile->update(); @@ -346,54 +327,53 @@ class ProfilesettingsAction extends SettingsAction $result = $user->update($original); if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); + $user->query('ROLLBACK'); // TRANS: Server error thrown when user profile settings could not be updated to // TRANS: automatically subscribe to any subscriber. - $this->serverError(_('Could not update user for autosubscribe or subscribe_policy.')); + throw new ServerException(_('Could not update user for autosubscribe or subscribe_policy.')); } // Re-initialize language environment if it changed common_init_language(); } - $profile = $user->getProfile(); + $original = clone($this->scoped); - $orig_profile = clone($profile); - - if (common_config('profile', 'changenick') == true && $profile->nickname !== $nickname) { + if (common_config('profile', 'changenick') == true && $this->scoped->getNickname() !== $nickname) { assert(Nickname::normalize($nickname)===$nickname); - common_debug("Changing user nickname from '{$profile->nickname}' to '{$nickname}'."); - $profile->nickname = $nickname; - $profile->profileurl = common_profile_url($profile->nickname); + common_debug("Changing user nickname from '{$this->scoped->getNickname()}' to '{$nickname}'."); + $this->scoped->nickname = $nickname; + $this->scoped->profileurl = common_profile_url($this->scoped->getNickname()); } - $profile->fullname = $fullname; - $profile->homepage = $homepage; - $profile->bio = $bio; - $profile->location = $location; + $this->scoped->fullname = $fullname; + $this->scoped->homepage = $homepage; + $this->scoped->bio = $bio; + $this->scoped->location = $location; $loc = Location::fromName($location); if (empty($loc)) { - $profile->lat = null; - $profile->lon = null; - $profile->location_id = null; - $profile->location_ns = null; + $this->scoped->lat = null; + $this->scoped->lon = null; + $this->scoped->location_id = null; + $this->scoped->location_ns = null; } else { - $profile->lat = $loc->lat; - $profile->lon = $loc->lon; - $profile->location_id = $loc->location_id; - $profile->location_ns = $loc->location_ns; + $this->scoped->lat = $loc->lat; + $this->scoped->lon = $loc->lon; + $this->scoped->location_id = $loc->location_id; + $this->scoped->location_ns = $loc->location_ns; } if (common_config('location', 'share') == 'user') { $exists = false; - $prefs = User_location_prefs::getKV('user_id', $user->id); + $prefs = User_location_prefs::getKV('user_id', $this->scoped->getID()); if (empty($prefs)) { $prefs = new User_location_prefs(); - $prefs->user_id = $user->id; + $prefs->user_id = $this->scoped->getID(); $prefs->created = common_sql_now(); } else { $exists = true; @@ -410,42 +390,37 @@ class ProfilesettingsAction extends SettingsAction if ($result === false) { common_log_db_error($prefs, ($exists) ? 'UPDATE' : 'INSERT', __FILE__); + $user->query('ROLLBACK'); // TRANS: Server error thrown when user profile location preference settings could not be updated. - $this->serverError(_('Could not save location prefs.')); + throw new ServerException(_('Could not save location prefs.')); } } - common_debug('Old profile: ' . common_log_objstring($orig_profile), __FILE__); - common_debug('New profile: ' . common_log_objstring($profile), __FILE__); + common_debug('Old profile: ' . common_log_objstring($original), __FILE__); + common_debug('New profile: ' . common_log_objstring($this->scoped), __FILE__); - $result = $profile->update($orig_profile); + $result = $this->scoped->update($original); if ($result === false) { - common_log_db_error($profile, 'UPDATE', __FILE__); + common_log_db_error($this->scoped, 'UPDATE', __FILE__); + $user->query('ROLLBACK'); // TRANS: Server error thrown when user profile settings could not be saved. - $this->serverError(_('Could not save profile.')); + throw new ServerException(_('Could not save profile.')); } // Set the user tags - $result = $user->setSelfTags($tags, $tag_priv); - - if (!$result) { - // TRANS: Server error thrown when user profile settings tags could not be saved. - $this->serverError(_('Could not save tags.')); - } + $result = Profile_tag::setSelfTags($this->scoped, $tags, $tag_priv); $user->query('COMMIT'); Event::handle('EndProfileSaveForm', array($this)); // TRANS: Confirmation shown when user profile settings are saved. - $this->showForm(_('Settings saved.'), true); + return _('Settings saved.'); } } function showAside() { - $user = common_current_user(); - $this->elementStart('div', array('id' => 'aside_primary', 'class' => 'aside')); @@ -453,7 +428,7 @@ class ProfilesettingsAction extends SettingsAction 'class' => 'section')); $this->elementStart('ul'); if (Event::handle('StartProfileSettingsActions', array($this))) { - if ($user->hasRight(Right::BACKUPACCOUNT)) { + if ($this->scoped->hasRight(Right::BACKUPACCOUNT)) { $this->elementStart('li'); $this->element('a', array('href' => common_local_url('backupaccount')), @@ -461,7 +436,7 @@ class ProfilesettingsAction extends SettingsAction _('Backup account')); $this->elementEnd('li'); } - if ($user->hasRight(Right::DELETEACCOUNT)) { + if ($this->scoped->hasRight(Right::DELETEACCOUNT)) { $this->elementStart('li'); $this->element('a', array('href' => common_local_url('deleteaccount')), @@ -469,7 +444,7 @@ class ProfilesettingsAction extends SettingsAction _('Delete account')); $this->elementEnd('li'); } - if ($user->hasRight(Right::RESTOREACCOUNT)) { + if ($this->scoped->hasRight(Right::RESTOREACCOUNT)) { $this->elementStart('li'); $this->element('a', array('href' => common_local_url('restoreaccount')), diff --git a/classes/Profile.php b/classes/Profile.php index 5709a15d01..5359ffb58b 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -1407,6 +1407,11 @@ class Profile extends Managed_DataObject return $this->fullname; } + public function getHomepage() + { + return $this->homepage; + } + public function getDescription() { return $this->bio; diff --git a/classes/Profile_tag.php b/classes/Profile_tag.php index 6d6f49bedc..8034e68ccf 100644 --- a/classes/Profile_tag.php +++ b/classes/Profile_tag.php @@ -2,22 +2,15 @@ /** * Table Definition for profile_tag */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Profile_tag extends Managed_DataObject { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - public $__table = 'profile_tag'; // table name public $tagger; // int(4) primary_key not_null public $tagged; // int(4) primary_key not_null public $tag; // varchar(64) primary_key not_null public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - public static function schemaDef() { return array( @@ -52,6 +45,16 @@ class Profile_tag extends Managed_DataObject return Profile_list::pkeyGet(array('tagger' => $this->tagger, 'tag' => $this->tag)); } + static function getSelfTagsArray(Profile $target) + { + return self::getTagsArray($target->getID(), $target->getID(), $target); + } + + static function setSelfTags(Profile $target, array $newtags, array $privacy=array()) + { + return self::setTags($target->getID(), $target->getID(), $newtags, $privacy); + } + static function getTags($tagger, $tagged, $auth_user=null) { $profile_list = new Profile_list(); @@ -88,7 +91,7 @@ class Profile_tag extends Managed_DataObject return $profile_list; } - static function getTagsArray($tagger, $tagged, $auth_user_id=null) + static function getTagsArray($tagger, $tagged, Profile $scoped=null) { $ptag = new Profile_tag(); @@ -100,7 +103,7 @@ class Profile_tag extends Managed_DataObject 'and profile_tag.tagged = %d ', $tagger, $tagged); - if ($auth_user_id != $tagger) { + if (!$scoped instanceof Profile || $scoped->getID() !== $tagger) { $qry .= 'and profile_list.private = 0'; } @@ -115,10 +118,10 @@ class Profile_tag extends Managed_DataObject return $tags; } - static function setTags($tagger, $tagged, $newtags, $privacy=array()) { + static function setTags($tagger, $tagged, array $newtags, array $privacy=array()) { $newtags = array_unique($newtags); - $oldtags = self::getTagsArray($tagger, $tagged, $tagger); + $oldtags = self::getTagsArray($tagger, $tagged, Profile::getByID($tagger)); $ptag = new Profile_tag(); @@ -149,19 +152,18 @@ class Profile_tag extends Managed_DataObject 'tag' => $tag)); # if tag already exists, return it - if(!empty($ptag)) { + if ($ptag instanceof Profile_tag) { return $ptag; } - $tagger_profile = Profile::getKV('id', $tagger); - $tagged_profile = Profile::getKV('id', $tagged); + $tagger_profile = Profile::getByID($tagger); + $tagged_profile = Profile::getByID($tagged); if (Event::handle('StartTagProfile', array($tagger_profile, $tagged_profile, $tag))) { if (!$tagger_profile->canTag($tagged_profile)) { // TRANS: Client exception thrown trying to set a tag for a user that cannot be tagged. throw new ClientException(_('You cannot tag this user.')); - return false; } $tags = new Profile_list(); @@ -174,7 +176,6 @@ class Profile_tag extends Managed_DataObject 'which is the maximum allowed number of tags. ' . 'Try using or deleting some existing tags.'), common_config('peopletag', 'maxtags'))); - return false; } $plist = new Profile_list(); @@ -188,7 +189,6 @@ class Profile_tag extends Managed_DataObject 'which is the maximum allowed number. ' . 'Try unlisting others first.'), common_config('peopletag', 'maxpeople'), $tag)); - return false; } $newtag = new Profile_tag(); @@ -199,9 +199,9 @@ class Profile_tag extends Managed_DataObject $result = $newtag->insert(); - if (!$result) { common_log_db_error($newtag, 'INSERT', __FILE__); + $plist->query('ROLLBACK'); return false; } @@ -212,7 +212,6 @@ class Profile_tag extends Managed_DataObject $newtag->delete(); $profile_list->delete(); throw $e; - return false; } $profile_list->taggedCount(true); @@ -233,20 +232,17 @@ class Profile_tag extends Managed_DataObject if (Event::handle('StartUntagProfile', array($ptag))) { $orig = clone($ptag); $result = $ptag->delete(); - if (!$result) { + if ($result === false) { common_log_db_error($this, 'DELETE', __FILE__); return false; } Event::handle('EndUntagProfile', array($orig)); - if ($result) { - $profile_list = Profile_list::pkeyGet(array('tag' => $tag, 'tagger' => $tagger)); - if (!empty($profile_list)) { - $profile_list->taggedCount(true); - } - self::blowCaches($tagger, $tagged); - return true; + $profile_list = Profile_list::pkeyGet(array('tag' => $tag, 'tagger' => $tagger)); + if (!empty($profile_list)) { + $profile_list->taggedCount(true); } - return false; + self::blowCaches($tagger, $tagged); + return true; } } diff --git a/classes/User.php b/classes/User.php index 175d945401..5b9d7b51fe 100644 --- a/classes/User.php +++ b/classes/User.php @@ -462,16 +462,6 @@ class User extends Managed_DataObject return $this->getProfile()->getNotices($offset, $limit, $since_id, $before_id); } - function getSelfTags() - { - return Profile_tag::getTagsArray($this->id, $this->id, $this->id); - } - - function setSelfTags($newtags, $privacy) - { - return Profile_tag::setTags($this->id, $this->id, $newtags, $privacy); - } - function block(Profile $other) { // Add a new block record From 9045575e62f88644e7396a81883e898d808f5d3f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 12:46:09 +0200 Subject: [PATCH 070/156] ExtendedProfile now works better as extended ProfilesettingsAction --- .../actions/profiledetailsettings.php | 230 +++++++----------- .../ExtendedProfile/lib/extendedprofile.php | 4 +- 2 files changed, 85 insertions(+), 149 deletions(-) diff --git a/plugins/ExtendedProfile/actions/profiledetailsettings.php b/plugins/ExtendedProfile/actions/profiledetailsettings.php index 8d8a33ec44..73bf364c7b 100644 --- a/plugins/ExtendedProfile/actions/profiledetailsettings.php +++ b/plugins/ExtendedProfile/actions/profiledetailsettings.php @@ -17,9 +17,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } class ProfileDetailSettingsAction extends ProfileSettingsAction { @@ -29,18 +27,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return _m('Extended profile settings'); } - /** - * Instructions for use - * - * @return instructions for use - */ - function getInstructions() - { - // TRANS: Usage instructions for profile settings. - return _m('You can update your personal profile info here '. - 'so people know more about you.'); - } - function showStylesheets() { parent::showStylesheets(); $this->cssLink('plugins/ExtendedProfile/css/profiledetail.css'); @@ -53,36 +39,21 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return true; } - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm( - // TRANS: Client error displayed when the session token does not match or is not given. - _m('There was a problem with your session token. ' - . 'Try again, please.' - ) - ); - return; + if ($this->arg('save')) { + return $this->saveDetails(); } - if ($this->arg('save')) { - $this->saveDetails(); - } else { - // TRANS: Message given submitting a form with an unknown action. - $this->showForm(_m('Unexpected form submission.')); - } + // TRANS: Message given submitting a form with an unknown action. + throw new ClientException(_m('Unexpected form submission.')); } function showContent() { - $cur = common_current_user(); - $profile = $cur->getProfile(); - $widget = new ExtendedProfileWidget( $this, - $profile, + $this->scoped, ExtendedProfileWidget::EDITABLE ); $widget->show(); @@ -92,49 +63,38 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction { common_debug(var_export($_POST, true)); - $user = common_current_user(); + $this->saveStandardProfileDetails(); - try { - $this->saveStandardProfileDetails($user); + $simpleFieldNames = array('title', 'spouse', 'kids', 'manager'); + $dateFieldNames = array('birthday'); - $profile = $user->getProfile(); - - $simpleFieldNames = array('title', 'spouse', 'kids', 'manager'); - $dateFieldNames = array('birthday'); - - foreach ($simpleFieldNames as $name) { - $value = $this->trimmed('extprofile-' . $name); - if (!empty($value)) { - $this->saveField($user, $name, $value); - } + foreach ($simpleFieldNames as $name) { + $value = $this->trimmed('extprofile-' . $name); + if (!empty($value)) { + $this->saveField($name, $value); } - - foreach ($dateFieldNames as $name) { - $value = $this->trimmed('extprofile-' . $name); - $dateVal = $this->parseDate($name, $value); - $this->saveField( - $user, - $name, - null, - null, - null, - $dateVal - ); - } - - $this->savePhoneNumbers($user); - $this->saveIms($user); - $this->saveWebsites($user); - $this->saveExperiences($user); - $this->saveEducations($user); - - } catch (Exception $e) { - $this->showForm($e->getMessage(), false); - return; } + foreach ($dateFieldNames as $name) { + $value = $this->trimmed('extprofile-' . $name); + $dateVal = $this->parseDate($name, $value); + $this->saveField( + $name, + null, + null, + null, + $dateVal + ); + } + + $this->savePhoneNumbers(); + $this->saveIms(); + $this->saveWebsites(); + $this->saveExperiences(); + $this->saveEducations(); + // TRANS: Success message after saving extended profile details. - $this->showForm(_m('Details saved.'), true); + return _m('Details saved.'); } @@ -168,15 +128,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return null; } - function savePhoneNumbers($user) { + function savePhoneNumbers() { $phones = $this->findPhoneNumbers(); - $this->removeAll($user, 'phone'); + $this->removeAll('phone'); $i = 0; foreach($phones as $phone) { if (!empty($phone['value'])) { ++$i; $this->saveField( - $user, 'phone', $phone['value'], $phone['rel'], @@ -226,15 +185,14 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $imArray; } - function saveIms($user) { + function saveIms() { $ims = $this->findIms(); - $this->removeAll($user, 'im'); + $this->removeAll('im'); $i = 0; foreach($ims as $im) { if (!empty($im['value'])) { ++$i; $this->saveField( - $user, 'im', $im['value'], $im['rel'], @@ -262,9 +220,9 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $wsArray; } - function saveWebsites($user) { + function saveWebsites() { $sites = $this->findWebsites(); - $this->removeAll($user, 'website'); + $this->removeAll('website'); $i = 0; foreach($sites as $site) { if (!empty($site['value']) && !common_valid_http_url($site['value'])) { @@ -276,7 +234,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction if (!empty($site['value'])) { ++$i; $this->saveField( - $user, 'website', $site['value'], $site['rel'], @@ -317,20 +274,19 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction return $expArray; } - function saveExperiences($user) { + function saveExperiences() { common_debug('save experiences'); $experiences = $this->findExperiences(); - $this->removeAll($user, 'company'); - $this->removeAll($user, 'start'); - $this->removeAll($user, 'end'); // also stores 'current' + $this->removeAll('company'); + $this->removeAll('start'); + $this->removeAll('end'); // also stores 'current' $i = 0; foreach($experiences as $experience) { if (!empty($experience['company'])) { ++$i; $this->saveField( - $user, 'company', $experience['company'], null, @@ -338,7 +294,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction ); $this->saveField( - $user, 'start', null, null, @@ -349,7 +304,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction // Save "current" employer indicator in rel if ($experience['current']) { $this->saveField( - $user, 'end', null, 'current', // rel @@ -357,7 +311,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction ); } else { $this->saveField( - $user, 'end', null, null, @@ -399,44 +352,40 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } - function saveEducations($user) { + function saveEducations() { common_debug('save education'); $edus = $this->findEducations(); common_debug(var_export($edus, true)); - $this->removeAll($user, 'school'); - $this->removeAll($user, 'degree'); - $this->removeAll($user, 'degree_descr'); - $this->removeAll($user, 'school_start'); - $this->removeAll($user, 'school_end'); + $this->removeAll('school'); + $this->removeAll('degree'); + $this->removeAll('degree_descr'); + $this->removeAll('school_start'); + $this->removeAll('school_end'); $i = 0; foreach($edus as $edu) { if (!empty($edu['school'])) { ++$i; $this->saveField( - $user, 'school', $edu['school'], null, $i ); $this->saveField( - $user, 'degree', $edu['degree'], null, $i ); $this->saveField( - $user, 'degree_descr', $edu['description'], null, $i ); $this->saveField( - $user, 'school_start', null, null, @@ -445,7 +394,6 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction ); $this->saveField( - $user, 'school_end', null, null, @@ -491,35 +439,33 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction /** * Save an extended profile field as a Profile_detail * - * @param User $user the current user * @param string $name field name * @param string $value field value * @param string $rel field rel (type) * @param int $index index (fields can have multiple values) * @param date $date related date */ - function saveField($user, $name, $value, $rel = null, $index = null, $date = null) + function saveField($name, $value, $rel = null, $index = null, $date = null) { - $profile = $user->getProfile(); $detail = new Profile_detail(); - $detail->profile_id = $profile->id; + $detail->profile_id = $this->scoped->getID(); $detail->field_name = $name; $detail->value_index = $index; $result = $detail->find(true); - if (empty($result)) { - $detial->value_index = $index; + if (!$result instanceof Profile_detail) { + $detail->value_index = $index; $detail->rel = $rel; $detail->field_value = $value; $detail->date = $date; $detail->created = common_sql_now(); $result = $detail->insert(); - if (empty($result)) { + if ($result === false) { common_log_db_error($detail, 'INSERT', __FILE__); // TRANS: Server error displayed when a field could not be saved in the database. - $this->serverError(_m('Could not save profile details.')); + throw new ServerException(_m('Could not save profile details.')); } } else { $orig = clone($detail); @@ -529,21 +475,20 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction $detail->date = $date; $result = $detail->update($orig); - if (empty($result)) { + if ($result === false) { common_log_db_error($detail, 'UPDATE', __FILE__); // TRANS: Server error displayed when a field could not be saved in the database. - $this->serverError(_m('Could not save profile details.')); + throw new ServerException(_m('Could not save profile details.')); } } $detail->free(); } - function removeAll($user, $name) + function removeAll($name) { - $profile = $user->getProfile(); $detail = new Profile_detail(); - $detail->profile_id = $profile->id; + $detail->profile_id = $this->scoped->getID(); $detail->field_name = $name; $detail->delete(); $detail->free(); @@ -554,10 +499,8 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction * * XXX: There's a lot of dupe code here from ProfileSettingsAction. * Do not want. - * - * @param User $user the current user */ - function saveStandardProfileDetails($user) + function saveStandardProfileDetails() { $fullname = $this->trimmed('extprofile-fullname'); $location = $this->trimmed('extprofile-location'); @@ -581,54 +524,47 @@ class ProfileDetailSettingsAction extends ProfileSettingsAction } } - $profile = $user->getProfile(); - - $oldTags = $user->getSelfTags(); + $oldTags = Profile_tag::getSelfTagsArray($this->scoped); $newTags = array_diff($tags, $oldTags); - if ($fullname != $profile->fullname - || $location != $profile->location + if ($fullname != $this->scoped->getFullname() + || $location != $this->scoped->location || !empty($newTags) - || $bio != $profile->bio) { + || $bio != $this->scoped->getDescription()) { - $orig = clone($profile); + $orig = clone($this->scoped); - $profile->nickname = $user->nickname; - $profile->fullname = $fullname; - $profile->bio = $bio; - $profile->location = $location; + // Skipping nickname change here until we add logic for when the site allows it or not + // old Profilesettings will still let us do that. + + $this->scoped->fullname = $fullname; + $this->scoped->bio = $bio; + $this->scoped->location = $location; $loc = Location::fromName($location); if (empty($loc)) { - $profile->lat = null; - $profile->lon = null; - $profile->location_id = null; - $profile->location_ns = null; + $this->scoped->lat = null; + $this->scoped->lon = null; + $this->scoped->location_id = null; + $this->scoped->location_ns = null; } else { - $profile->lat = $loc->lat; - $profile->lon = $loc->lon; - $profile->location_id = $loc->location_id; - $profile->location_ns = $loc->location_ns; + $this->scoped->lat = $loc->lat; + $this->scoped->lon = $loc->lon; + $this->scoped->location_id = $loc->location_id; + $this->scoped->location_ns = $loc->location_ns; } - $profile->profileurl = common_profile_url($user->nickname); - - $result = $profile->update($orig); + $result = $this->scoped->update($orig); if ($result === false) { - common_log_db_error($profile, 'UPDATE', __FILE__); + common_log_db_error($this->scoped, 'UPDATE', __FILE__); // TRANS: Server error thrown when user profile settings could not be saved. - $this->serverError(_m('Could not save profile.')); + throw new ServerException(_m('Could not save profile.')); } // Set the user tags - $result = $user->setSelfTags($tags); - - if (!$result) { - // TRANS: Server error thrown when user profile settings tags could not be saved. - $this->serverError(_m('Could not save tags.')); - } + $result = Profile_tag::setSelfTags($this->scoped, $tags); Event::handle('EndProfileSaveForm', array($this)); } diff --git a/plugins/ExtendedProfile/lib/extendedprofile.php b/plugins/ExtendedProfile/lib/extendedprofile.php index d73170b235..3682e6fc9d 100644 --- a/plugins/ExtendedProfile/lib/extendedprofile.php +++ b/plugins/ExtendedProfile/lib/extendedprofile.php @@ -52,7 +52,7 @@ class ExtendedProfile function loadFields() { $detail = new Profile_detail(); - $detail->profile_id = $this->profile->id; + $detail->profile_id = $this->profile->getID(); $detail->find(); $fields = array(); @@ -71,7 +71,7 @@ class ExtendedProfile */ function getTags() { - return implode(' ', $this->user->getSelfTags()); + return implode(' ', Profile_tag::getSelfTagsArray($this->profile)); } /** From a093dea38c9a6b5af6d169ccfb5ea14eec76ada1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 12:46:24 +0200 Subject: [PATCH 071/156] ExtendedProfile is not something we want by default. --- lib/siteprofile.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/siteprofile.php b/lib/siteprofile.php index 4bbffea754..a2c09efb2f 100644 --- a/lib/siteprofile.php +++ b/lib/siteprofile.php @@ -110,7 +110,6 @@ class PublicSite extends SiteProfileSettings 'plugins' => array( 'core' => self::corePlugins(), 'default' => array_merge(self::defaultPlugins(), array( - 'ExtendedProfile' => array(), 'RegisterThrottle' => array(), )) ), From 2dd979d3f83e6e2a314308fdd2a205b02fa406a8 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 14:29:07 +0200 Subject: [PATCH 072/156] SMS Settings now better adapted to FormAction --- actions/smssettings.php | 168 +++++++++++++++------------------------- 1 file changed, 63 insertions(+), 105 deletions(-) diff --git a/actions/smssettings.php b/actions/smssettings.php index ec8841281d..ca6a7d04ef 100644 --- a/actions/smssettings.php +++ b/actions/smssettings.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Settings for SMS @@ -45,6 +43,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class SmssettingsAction extends SettingsAction { + protected function doPreparation() + { + if (!common_config('sms', 'enabled')) { + // TRANS: Message given in the SMS settings if SMS is not enabled on the site. + throw new ServerException(_('SMS is not available.')); + } + } + /** * Title of the page * @@ -86,14 +92,7 @@ class SmssettingsAction extends SettingsAction */ function showContent() { - if (!common_config('sms', 'enabled')) { - $this->element('div', array('class' => 'error'), - // TRANS: Message given in the SMS settings if SMS is not enabled on the site. - _('SMS is not available.')); - return; - } - - $user = common_current_user(); + $user = $this->scoped->getUser(); $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_sms', @@ -118,8 +117,8 @@ class SmssettingsAction extends SettingsAction // TRANS: Button label to remove a confirmed SMS address. $this->submit('remove', _m('BUTTON','Remove')); } else { - $confirm = $this->getConfirmation(); - if ($confirm) { + try { + $confirm = $this->getConfirmation(); $carrier = Sms_carrier::getKV($confirm->address_extra); $this->element('p', 'form_unconfirmed', $confirm->address . ' (' . $carrier->name . ')'); @@ -141,7 +140,7 @@ class SmssettingsAction extends SettingsAction $this->elementEnd('ul'); // TRANS: Button label to confirm SMS confirmation code in SMS settings. $this->submit('confirm', _m('BUTTON','Confirm')); - } else { + } catch (NoResultException $e) { $this->elementStart('ul', 'form_data'); $this->elementStart('li'); // TRANS: Field label for SMS phone number input in SMS settings form. @@ -216,60 +215,38 @@ class SmssettingsAction extends SettingsAction */ function getConfirmation() { - $user = common_current_user(); - $confirm = new Confirm_address(); - $confirm->user_id = $user->id; + $confirm->user_id = $this->scoped->getID(); $confirm->address_type = 'sms'; if ($confirm->find(true)) { return $confirm; - } else { - return null; } + + throw new NoResultException($confirm); } - /** - * Handle posts to this form - * - * Based on the button that was pressed, muxes out to other functions - * to do the actual task requested. - * - * All sub-functions reload the form with a message -- success or failure. - * - * @return void - */ - function handlePost() + protected function doPost() { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } if ($this->arg('save')) { - $this->savePreferences(); + return $this->savePreferences(); } else if ($this->arg('add')) { - $this->addAddress(); + return $this->addAddress(); } else if ($this->arg('cancel')) { - $this->cancelConfirmation(); + return $this->cancelConfirmation(); } else if ($this->arg('remove')) { - $this->removeAddress(); + return $this->removeAddress(); } else if ($this->arg('removeincoming')) { - $this->removeIncoming(); + return $this->removeIncoming(); } else if ($this->arg('newincoming')) { - $this->newIncoming(); + return $this->newIncoming(); } else if ($this->arg('confirm')) { - $this->confirmCode(); - } else { - // TRANS: Message given submitting a form with an unknown action in SMS settings. - $this->showForm(_('Unexpected form submission.')); + return $this->confirmCode(); } + // TRANS: Message given submitting a form with an unknown action in SMS settings. + throw new ClientException(_('Unexpected form submission.')); } /** @@ -281,30 +258,26 @@ class SmssettingsAction extends SettingsAction */ function savePreferences() { - $smsnotify = $this->boolean('smsnotify'); - - $user = common_current_user(); - - assert(!is_null($user)); // should already be checked + $user = $this->scoped->getUser(); $user->query('BEGIN'); $original = clone($user); - $user->smsnotify = $smsnotify; + $user->smsnotify = $this->boolean('smsnotify'); $result = $user->update($original); if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); // TRANS: Server error thrown on database error updating SMS preferences. - $this->serverError(_('Could not update user.')); + throw new ServerException(_('Could not update user.')); } $user->query('COMMIT'); // TRANS: Confirmation message for successful SMS preferences save. - $this->showForm(_('SMS preferences saved.'), true); + return _('SMS preferences saved.'); } /** @@ -324,28 +297,24 @@ class SmssettingsAction extends SettingsAction // Some validation - if (!$sms) { + if (empty($sms)) { // TRANS: Message given saving SMS phone number without having provided one. - $this->showForm(_('No phone number.')); - return; + throw new ClientException(_('No phone number.')); } - if (!$carrier_id) { + if (empty($carrier_id)) { // TRANS: Message given saving SMS phone number without having selected a carrier. - $this->showForm(_('No carrier selected.')); - return; + throw new ClientException(_('No carrier selected.')); } $sms = common_canonical_sms($sms); - if ($user->sms == $sms) { + if ($user->sms === $sms) { // TRANS: Message given saving SMS phone number that is already set. - $this->showForm(_('That is already your phone number.')); - return; + throw new AlreadyFulfilledException(_('That is already your phone number.')); } else if ($this->smsExists($sms)) { // TRANS: Message given saving SMS phone number that is already set for another user. - $this->showForm(_('That phone number already belongs to another user.')); - return; + throw new ClientException(_('That phone number already belongs to another user.')); } $confirm = new Confirm_address(); @@ -353,7 +322,7 @@ class SmssettingsAction extends SettingsAction $confirm->address = $sms; $confirm->address_extra = $carrier_id; $confirm->address_type = 'sms'; - $confirm->user_id = $user->id; + $confirm->user_id = $this->scoped->getID(); $confirm->code = common_confirmation_code(40); $result = $confirm->insert(); @@ -371,11 +340,9 @@ class SmssettingsAction extends SettingsAction $carrier->toEmailAddress($sms)); // TRANS: Message given saving valid SMS phone number that is to be confirmed. - $msg = _('A confirmation code was sent to the phone number you added. '. + return _('A confirmation code was sent to the phone number you added. '. 'Check your phone for the code and instructions '. 'on how to use it.'); - - $this->showForm($msg, true); } /** @@ -390,29 +357,27 @@ class SmssettingsAction extends SettingsAction $sms = $this->trimmed('sms'); $carrier = $this->trimmed('carrier'); - $confirm = $this->getConfirmation(); - - if (!$confirm) { + try { + $confirm = $this->getConfirmation(); + if ($confirm->address != $sms) { + // TRANS: Message given canceling SMS phone number confirmation for the wrong phone number. + throw new ClientException(_('That is the wrong confirmation number.')); + } + } catch (NoResultException $e) { // TRANS: Message given canceling SMS phone number confirmation that is not pending. - $this->showForm(_('No pending confirmation to cancel.')); - return; - } - if ($confirm->address != $sms) { - // TRANS: Message given canceling SMS phone number confirmation for the wrong phone number. - $this->showForm(_('That is the wrong confirmation number.')); - return; + throw new AlreadyFulfilledException(_('No pending confirmation to cancel.')); } $result = $confirm->delete(); - if (!$result) { + if ($result === false) { common_log_db_error($confirm, 'DELETE', __FILE__); // TRANS: Server error thrown on database error canceling SMS phone number confirmation. - $this->serverError(_('Could not delete SMS confirmation.')); + throw new ServerException(_('Could not delete SMS confirmation.')); } // TRANS: Message given after successfully canceling SMS phone number confirmation. - $this->showForm(_('SMS confirmation cancelled.'), true); + return _('SMS confirmation cancelled.'); } /** @@ -422,7 +387,7 @@ class SmssettingsAction extends SettingsAction */ function removeAddress() { - $user = common_current_user(); + $user = $this->scoped->getUser(); $sms = $this->arg('sms'); $carrier = $this->arg('carrier'); @@ -432,8 +397,7 @@ class SmssettingsAction extends SettingsAction if ($user->sms != $sms) { // TRANS: Message given trying to remove an SMS phone number that is not // TRANS: registered for the active user. - $this->showForm(_('That is not your phone number.')); - return; + throw new ClientException(_('That is not your phone number.')); } $original = clone($user); @@ -446,7 +410,7 @@ class SmssettingsAction extends SettingsAction $user->updateWithKeys($original); // TRANS: Message given after successfully removing a registered SMS phone number. - $this->showForm(_('The SMS phone number was removed.'), true); + return _('The SMS phone number was removed.'); } /** @@ -460,15 +424,13 @@ class SmssettingsAction extends SettingsAction */ function smsExists($sms) { - $user = common_current_user(); - $other = User::getKV('sms', $sms); - if (!$other) { + if (!$other instanceof User) { return false; - } else { - return $other->id != $user->id; } + + return !$this->scoped->sameAs($other->getProfile()); } /** @@ -519,15 +481,12 @@ class SmssettingsAction extends SettingsAction { $code = $this->trimmed('code'); - if (!$code) { + if (empty($code)) { // TRANS: Message given saving SMS phone number confirmation code without having provided one. - $this->showForm(_('No code entered.')); - return; + throw new ClientException(_('No code entered.')); } - common_redirect(common_local_url('confirmaddress', - array('code' => $code)), - 303); + common_redirect(common_local_url('confirmaddress', array('code' => $code)), 303); } /** @@ -541,8 +500,7 @@ class SmssettingsAction extends SettingsAction if (!$user->incomingemail) { // TRANS: Form validation error displayed when trying to remove an incoming e-mail address while no address has been set. - $this->showForm(_('No incoming email address.')); - return; + throw new ClientException(_('No incoming email address.')); } $orig = clone($user); @@ -553,7 +511,7 @@ class SmssettingsAction extends SettingsAction $user->updateWithKeys($orig); // TRANS: Confirmation text after updating SMS settings. - $this->showForm(_('Incoming email address removed.'), true); + return _('Incoming email address removed.'); } /** @@ -565,7 +523,7 @@ class SmssettingsAction extends SettingsAction */ function newIncoming() { - $user = common_current_user(); + $user = $this->scoped->getUser(); $orig = clone($user); @@ -575,6 +533,6 @@ class SmssettingsAction extends SettingsAction $user->updateWithKeys($orig); // TRANS: Confirmation text after updating SMS settings. - $this->showForm(_('New incoming email address added.'), true); + return _('New incoming email address added.'); } } From 8d516d7f08c02f1144d2f43ffbf6536921fde29f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 14:40:09 +0200 Subject: [PATCH 073/156] Don't allow imports by default until it works well on large instances. --- lib/default.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/default.php b/lib/default.php index cf40f678ed..5bec29cc52 100644 --- a/lib/default.php +++ b/lib/default.php @@ -129,7 +129,7 @@ $default = 'biolimit' => null, 'changenick' => false, 'backup' => true, - 'restore' => true, + 'restore' => false, 'delete' => false, 'move' => true), 'image' => From 992fe6896fd70dd868b6df01fa3f870469f27259 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 15:16:45 +0200 Subject: [PATCH 074/156] Urlsettings now adapted to FormAction --- actions/urlsettings.php | 44 +++++++++++------------------------------ 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/actions/urlsettings.php b/actions/urlsettings.php index 69ad4c8690..c3e4ed50b2 100644 --- a/actions/urlsettings.php +++ b/actions/urlsettings.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Miscellaneous settings actions @@ -83,7 +81,7 @@ class UrlsettingsAction extends SettingsAction */ function showContent() { - $user = common_current_user(); + $user = $this->scoped->getUser(); $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_other', @@ -154,31 +152,13 @@ class UrlsettingsAction extends SettingsAction $this->elementEnd('form'); } - /** - * Handle a post - * - * Saves the changes to url-shortening prefs and shows a success or failure - * message. - * - * @return void - */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - $urlshorteningservice = $this->trimmed('urlshorteningservice'); if (!is_null($urlshorteningservice) && strlen($urlshorteningservice) > 50) { // TRANS: Form validation error for form "Other settings" in user profile. - $this->showForm(_('URL shortening service is too long (maximum 50 characters).')); - return; + throw new ClientException(_('URL shortening service is too long (maximum 50 characters).')); } $maxurllength = $this->trimmed('maxurllength'); @@ -195,9 +175,7 @@ class UrlsettingsAction extends SettingsAction throw new ClientException(_('Invalid number for maximum notice length.')); } - $user = common_current_user(); - - assert(!is_null($user)); // should already be checked + $user = $this->scoped->getUser(); $user->query('BEGIN'); @@ -209,14 +187,15 @@ class UrlsettingsAction extends SettingsAction if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); + $user->query('ROLLBACK'); // TRANS: Server error displayed when "Other" settings in user profile could not be updated on the server. - $this->serverError(_('Could not update user.')); + throw new ServerException(_('Could not update user.')); } $prefs = User_urlshortener_prefs::getPrefs($user); $orig = null; - if (empty($prefs)) { + if (!$prefs instanceof User_urlshortener_prefs) { $prefs = new User_urlshortener_prefs(); $prefs->user_id = $user->id; @@ -229,13 +208,14 @@ class UrlsettingsAction extends SettingsAction $prefs->maxurllength = $maxurllength; $prefs->maxnoticelength = $maxnoticelength; - if (!empty($orig)) { + if ($orig instanceof User_urlshortener_prefs) { $result = $prefs->update($orig); } else { $result = $prefs->insert(); } - if (!$result) { + if ($result === null) { + $user->query('ROLLBACK'); // TRANS: Server exception thrown in profile URL settings when preferences could not be saved. throw new ServerException(_('Error saving user URL shortening preferences.')); } @@ -243,6 +223,6 @@ class UrlsettingsAction extends SettingsAction $user->query('COMMIT'); // TRANS: Confirmation message after saving preferences. - $this->showForm(_('Preferences saved.'), true); + return _('Preferences saved.'); } } From be0c10e8f6f9c60b1cb840cda9ac6f42c8289738 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 15:24:37 +0200 Subject: [PATCH 075/156] Facebooksettings adapted to FormAction --- .../actions/facebooksettings.php | 65 ++++++------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/plugins/FacebookBridge/actions/facebooksettings.php b/plugins/FacebookBridge/actions/facebooksettings.php index 31e020a3ce..d6e4c14c4c 100644 --- a/plugins/FacebookBridge/actions/facebooksettings.php +++ b/plugins/FacebookBridge/actions/facebooksettings.php @@ -26,9 +26,7 @@ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Edit user settings for Facebook @@ -44,18 +42,9 @@ if (!defined('STATUSNET')) { class FacebooksettingsAction extends SettingsAction { private $facebook; // Facebook PHP-SDK client obj private $flink; - private $user; - - /** - * For initializing members of the class. - * - * @param array $argarray misc. arguments - * - * @return boolean true - */ - function prepare($args) { - parent::prepare($args); + protected function doPreparation() + { $this->facebook = new Facebook( array( 'appId' => common_config('facebook', 'appid'), @@ -64,36 +53,23 @@ class FacebooksettingsAction extends SettingsAction { ) ); - $this->user = common_current_user(); - $this->flink = Foreign_link::getByUserID( - $this->user->id, + $this->scoped->getID(), FACEBOOK_SERVICE ); return true; } - /* - * Check the sessions token and dispatch - */ - function handlePost($args) { - // CSRF protection - - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm( - // TRANS: Client error displayed when the session token does not match or is not given. - _m('There was a problem with your session token. Try again, please.') - ); - return; - } - + protected function doPost() + { if ($this->arg('save')) { - $this->saveSettings(); + return $this->saveSettings(); } else if ($this->arg('disconnect')) { - $this->disconnect(); + return $this->disconnect(); } + + throw new ClientException(_('No action to take on POST.')); } /** @@ -166,7 +142,7 @@ class FacebooksettingsAction extends SettingsAction { 'noticesync', // TRANS: Checkbox label in Facebook settings. _m('Publish my notices to Facebook.'), - ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND) : true + $this->flink->noticesync & FOREIGN_NOTICE_SEND ); $this->elementEnd('li'); @@ -177,7 +153,7 @@ class FacebooksettingsAction extends SettingsAction { 'replysync', // TRANS: Checkbox label in Facebook settings. _m('Send "@" replies to Facebook.'), - ($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true + $this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY ); $this->elementEnd('li'); @@ -196,7 +172,7 @@ class FacebooksettingsAction extends SettingsAction { // TRANS: Fieldset legend for form to disconnect from Facebook. $this->element('legend', null, _m('Disconnect my account from Facebook')); - if (empty($this->user->password)) { + if (!$this->scoped->hasPassword()) { $this->elementStart('p', array('class' => 'form_guide')); $msg = sprintf( @@ -242,11 +218,10 @@ class FacebooksettingsAction extends SettingsAction { if ($result === false) { // TRANS: Notice in case saving of synchronisation preferences fail. - $this->showForm(_m('There was a problem saving your sync preferences.')); - } else { - // TRANS: Confirmation that synchronisation settings have been saved into the system. - $this->showForm(_m('Sync preferences saved.'), true); + throw new ServerException(_m('There was a problem saving your sync preferences.')); } + // TRANS: Confirmation that synchronisation settings have been saved into the system. + return _m('Sync preferences saved.'); } /* @@ -258,12 +233,12 @@ class FacebooksettingsAction extends SettingsAction { $this->flink = null; if ($result === false) { - common_log_db_error($user, 'DELETE', __FILE__); + common_log_db_error($this->flink, 'DELETE', __FILE__); // TRANS: Server error displayed when deleting the link to a Facebook account fails. - $this->serverError(_m('Could not delete link to Facebook.')); + throw new ServerException(_m('Could not delete link to Facebook.')); } - // TRANS: Confirmation message. StatusNet account was unlinked from Facebook. - $this->showForm(_m('You have disconnected from Facebook.'), true); + // TRANS: Confirmation message. GNU social account was unlinked from Facebook. + return _m('You have disconnected this account from Facebook.'); } } From 8d2504a809605e6bef61c13fc614dc0a57207385 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 15:27:47 +0200 Subject: [PATCH 076/156] Early return in FacebookBridge settings action --- .../actions/facebooksettings.php | 178 +++++++++--------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/plugins/FacebookBridge/actions/facebooksettings.php b/plugins/FacebookBridge/actions/facebooksettings.php index d6e4c14c4c..67dd20e036 100644 --- a/plugins/FacebookBridge/actions/facebooksettings.php +++ b/plugins/FacebookBridge/actions/facebooksettings.php @@ -41,7 +41,8 @@ if (!defined('GNUSOCIAL')) { exit(1); } */ class FacebooksettingsAction extends SettingsAction { private $facebook; // Facebook PHP-SDK client obj - private $flink; + + protected $flink; protected function doPreparation() { @@ -57,8 +58,6 @@ class FacebooksettingsAction extends SettingsAction { $this->scoped->getID(), FACEBOOK_SERVICE ); - - return true; } protected function doPost() @@ -98,109 +97,110 @@ class FacebooksettingsAction extends SettingsAction { * @return void */ function showContent() { - if (!empty($this->flink)) { + if (!$this->flink instanceof Foreign_link) { + throw new ServerException(_m('You have not linked this account to Facebook.')); + } - $this->elementStart( - 'form', - array( - 'method' => 'post', - 'id' => 'form_settings_facebook', - 'class' => 'form_settings', - 'action' => common_local_url('facebooksettings') - ) - ); + $this->elementStart( + 'form', + array( + 'method' => 'post', + 'id' => 'form_settings_facebook', + 'class' => 'form_settings', + 'action' => common_local_url('facebooksettings') + ) + ); - $this->hidden('token', common_session_token()); + $this->hidden('token', common_session_token()); - // TRANS: Form note. User is connected to facebook. - $this->element('p', 'form_note', _m('Connected Facebook user')); + // TRANS: Form note. User is connected to facebook. + $this->element('p', 'form_note', _m('Connected Facebook user')); - $this->elementStart('p', array('class' => 'facebook-user-display')); + $this->elementStart('p', array('class' => 'facebook-user-display')); - $this->element( - 'fb:profile-pic', - array( - 'uid' => $this->flink->foreign_id, - 'size' => 'small', - 'linked' => 'true', - 'facebook-logo' => 'true' - ) - ); + $this->element( + 'fb:profile-pic', + array( + 'uid' => $this->flink->foreign_id, + 'size' => 'small', + 'linked' => 'true', + 'facebook-logo' => 'true' + ) + ); - $this->element( - 'fb:name', - array('uid' => $this->flink->foreign_id, 'useyou' => 'false') - ); + $this->element( + 'fb:name', + array('uid' => $this->flink->foreign_id, 'useyou' => 'false') + ); - $this->elementEnd('p'); + $this->elementEnd('p'); - $this->elementStart('ul', 'form_data'); + $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); + $this->elementStart('li'); - $this->checkbox( - 'noticesync', + $this->checkbox( + 'noticesync', + // TRANS: Checkbox label in Facebook settings. + _m('Publish my notices to Facebook.'), + $this->flink->noticesync & FOREIGN_NOTICE_SEND + ); + + $this->elementEnd('li'); + + $this->elementStart('li'); + + $this->checkbox( + 'replysync', // TRANS: Checkbox label in Facebook settings. - _m('Publish my notices to Facebook.'), - $this->flink->noticesync & FOREIGN_NOTICE_SEND + _m('Send "@" replies to Facebook.'), + $this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY + ); + + $this->elementEnd('li'); + + $this->elementStart('li'); + + // TRANS: Submit button to save synchronisation settings. + $this->submit('save', _m('BUTTON', 'Save')); + + $this->elementEnd('li'); + + $this->elementEnd('ul'); + + $this->elementStart('fieldset'); + + // TRANS: Fieldset legend for form to disconnect from Facebook. + $this->element('legend', null, _m('Disconnect my account from Facebook')); + + if (!$this->scoped->hasPassword()) { + $this->elementStart('p', array('class' => 'form_guide')); + + $msg = sprintf( + // TRANS: Notice in disconnect from Facebook form if user has no local StatusNet password. + _m('Disconnecting your Faceboook would make it impossible to '. + 'log in! Please [set a password](%s) first.'), + common_local_url('passwordsettings') ); - $this->elementEnd('li'); - - $this->elementStart('li'); - - $this->checkbox( - 'replysync', - // TRANS: Checkbox label in Facebook settings. - _m('Send "@" replies to Facebook.'), - $this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY + $this->raw(common_markup_to_html($msg)); + $this->elementEnd('p'); + } else { + // @todo FIXME: i18n: This message is not being used. + // TRANS: Message displayed when initiating disconnect of a StatusNet user + // TRANS: from a Facebook account. %1$s is the StatusNet site name. + $msg = sprintf(_m('Keep your %1$s account but disconnect from Facebook. ' . + 'You\'ll use your %1$s password to log in.'), + common_config('site', 'name') ); - $this->elementEnd('li'); + // TRANS: Submit button. + $this->submit('disconnect', _m('BUTTON', 'Disconnect')); + } - $this->elementStart('li'); + $this->elementEnd('fieldset'); - // TRANS: Submit button to save synchronisation settings. - $this->submit('save', _m('BUTTON', 'Save')); - - $this->elementEnd('li'); - - $this->elementEnd('ul'); - - $this->elementStart('fieldset'); - - // TRANS: Fieldset legend for form to disconnect from Facebook. - $this->element('legend', null, _m('Disconnect my account from Facebook')); - - if (!$this->scoped->hasPassword()) { - $this->elementStart('p', array('class' => 'form_guide')); - - $msg = sprintf( - // TRANS: Notice in disconnect from Facebook form if user has no local StatusNet password. - _m('Disconnecting your Faceboook would make it impossible to '. - 'log in! Please [set a password](%s) first.'), - common_local_url('passwordsettings') - ); - - $this->raw(common_markup_to_html($msg)); - $this->elementEnd('p'); - } else { - // @todo FIXME: i18n: This message is not being used. - // TRANS: Message displayed when initiating disconnect of a StatusNet user - // TRANS: from a Facebook account. %1$s is the StatusNet site name. - $msg = sprintf(_m('Keep your %1$s account but disconnect from Facebook. ' . - 'You\'ll use your %1$s password to log in.'), - common_config('site', 'name') - ); - - // TRANS: Submit button. - $this->submit('disconnect', _m('BUTTON', 'Disconnect')); - } - - $this->elementEnd('fieldset'); - - $this->elementEnd('form'); - } + $this->elementEnd('form'); } /* From da168674f9bef92d700f644e3232b28de794afe7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 16:08:22 +0200 Subject: [PATCH 077/156] OpenID settings aligned with FormAction --- plugins/OpenID/actions/openidsettings.php | 114 ++++++++-------------- plugins/OpenID/openid.php | 18 ++-- 2 files changed, 50 insertions(+), 82 deletions(-) diff --git a/plugins/OpenID/actions/openidsettings.php b/plugins/OpenID/actions/openidsettings.php index 0c20dac4cd..bf5d8886f1 100644 --- a/plugins/OpenID/actions/openidsettings.php +++ b/plugins/OpenID/actions/openidsettings.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } require_once INSTALLDIR.'/plugins/OpenID/openid.php'; @@ -86,8 +84,6 @@ class OpenidsettingsAction extends SettingsAction */ function showContent() { - $user = common_current_user(); - if (!common_config('openid', 'trusted_provider')) { $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_openid_add', @@ -115,7 +111,7 @@ class OpenidsettingsAction extends SettingsAction } $oid = new User_openid(); - $oid->user_id = $user->id; + $oid->user_id = $this->scoped->getID(); $cnt = $oid->find(); @@ -123,7 +119,7 @@ class OpenidsettingsAction extends SettingsAction // TRANS: Header on OpenID settings page. $this->element('h2', null, _m('HEADER','Remove OpenID')); - if ($cnt == 1 && !$user->password) { + if ($cnt == 1 && !$this->scoped->hasPassword()) { $this->element('p', 'form_guide', // TRANS: Form guide. @@ -184,7 +180,7 @@ class OpenidsettingsAction extends SettingsAction 'this list to deny it access to your OpenID.')); $this->elementStart('ul', 'form_data'); $user_openid_trustroot = new User_openid_trustroot(); - $user_openid_trustroot->user_id=$user->id; + $user_openid_trustroot->user_id = $this->scoped->getID(); if($user_openid_trustroot->find()) { while($user_openid_trustroot->fetch()) { $this->elementStart('li'); @@ -203,7 +199,7 @@ class OpenidsettingsAction extends SettingsAction $this->submit('settings_openid_trustroots_action-submit', _m('BUTTON','Remove'), 'submit', 'remove_trustroots'); $this->elementEnd('fieldset'); - $prefs = User_openid_prefs::getKV('user_id', $user->id); + $prefs = User_openid_prefs::getKV('user_id', $this->scoped->getID()); $this->elementStart('fieldset'); $this->element('legend', null, _m('LEGEND','Preferences')); @@ -224,38 +220,29 @@ class OpenidsettingsAction extends SettingsAction * * @return void */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_m('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if ($this->arg('add')) { if (common_config('openid', 'trusted_provider')) { // TRANS: Form validation error if no OpenID providers can be added. - $this->showForm(_m('Cannot add new providers.')); + throw new ServerException(_m('Cannot add new providers.')); } else { - $result = oid_authenticate($this->trimmed('openid_url'), - 'finishaddopenid'); + $result = oid_authenticate($this->trimmed('openid_url'), 'finishaddopenid'); if (is_string($result)) { // error message - $this->showForm($result); + throw new ServerException($result); } + return _('Added new provider.'); } } else if ($this->arg('remove')) { - $this->removeOpenid(); + return $this->removeOpenid(); } else if($this->arg('remove_trustroots')) { - $this->removeTrustroots(); + return $this->removeTrustroots(); } else if($this->arg('save_prefs')) { - $this->savePrefs(); - } else { - // TRANS: Unexpected form validation error. - $this->showForm(_m('Something weird happened.')); + return $this->savePrefs(); } + + // TRANS: Unexpected form validation error. + throw new ServerException(_m('No known action for POST.')); } /** @@ -268,26 +255,20 @@ class OpenidsettingsAction extends SettingsAction */ function removeTrustroots() { - $user = common_current_user(); - $trustroots = $this->arg('openid_trustroot'); - if($trustroots) { - foreach($trustroots as $trustroot) { - $user_openid_trustroot = User_openid_trustroot::pkeyGet( - array('user_id'=>$user->id, 'trustroot'=>$trustroot)); - if($user_openid_trustroot) { - $user_openid_trustroot->delete(); - } else { - // TRANS: Form validation error when trying to remove a non-existing trustroot. - $this->showForm(_m('No such OpenID trustroot.')); - return; - } + $trustroots = $this->arg('openid_trustroot', array()); + foreach($trustroots as $trustroot) { + $user_openid_trustroot = User_openid_trustroot::pkeyGet( + array('user_id'=>$this->scoped->getID(), 'trustroot'=>$trustroot)); + if($user_openid_trustroot) { + $user_openid_trustroot->delete(); + } else { + // TRANS: Form validation error when trying to remove a non-existing trustroot. + throw new ClientException(_m('No such OpenID trustroot.')); } - // TRANS: Success message after removing trustroots. - $this->showForm(_m('Trustroots removed.'), true); - } else { - $this->showForm(); } - return; + + // TRANS: Success message after removing trustroots. + return _m('Trustroots removed.'); } /** @@ -300,25 +281,19 @@ class OpenidsettingsAction extends SettingsAction */ function removeOpenid() { - $openid_url = $this->trimmed('openid_url'); + $oid = User_openid::getKV('canonical', $this->trimmed('openid_url')); - $oid = User_openid::getKV('canonical', $openid_url); - - if (!$oid) { + if (!$oid instanceof User_openid) { // TRANS: Form validation error for a non-existing OpenID. - $this->showForm(_m('No such OpenID.')); - return; + throw new ClientException(_m('No such OpenID.')); } - $cur = common_current_user(); - if (!$cur || $oid->user_id != $cur->id) { + if ($this->scoped->getID() !== $oid->user_id) { // TRANS: Form validation error if OpenID is connected to another user. - $this->showForm(_m('That OpenID does not belong to you.')); - return; + throw new ClientException(_m('That OpenID does not belong to you.')); } $oid->delete(); // TRANS: Success message after removing an OpenID. - $this->showForm(_m('OpenID removed.'), true); - return; + return _m('OpenID removed.'); } /** @@ -331,18 +306,12 @@ class OpenidsettingsAction extends SettingsAction */ function savePrefs() { - $cur = common_current_user(); - - if (empty($cur)) { - throw new ClientException(_("Not logged in.")); - } - $orig = null; - $prefs = User_openid_prefs::getKV('user_id', $cur->id); + $prefs = User_openid_prefs::getKV('user_id', $this->scoped->getID()); - if (empty($prefs)) { + if (!$prefs instanceof User_openid_prefs) { $prefs = new User_openid_prefs(); - $prefs->user_id = $cur->id; + $prefs->user_id = $this->scoped->getID(); $prefs->created = common_sql_now(); } else { $orig = clone($prefs); @@ -350,13 +319,12 @@ class OpenidsettingsAction extends SettingsAction $prefs->hide_profile_link = $this->booleanintstring('hide_profile_link'); - if (empty($orig)) { - $prefs->insert(); - } else { + if ($orig instanceof User_openid_prefs) { $prefs->update($orig); + } else { + $prefs->insert(); } - $this->showForm(_m('OpenID preferences saved.'), true); - return; + return _m('OpenID preferences saved.'); } } diff --git a/plugins/OpenID/openid.php b/plugins/OpenID/openid.php index 91a34bd6e3..ee854e8140 100644 --- a/plugins/OpenID/openid.php +++ b/plugins/OpenID/openid.php @@ -131,13 +131,15 @@ function oid_check_immediate($openid_url, $backto=null) function oid_authenticate($openid_url, $returnto, $immediate=false) { + if (!common_valid_http_url($openid_url)) { + throw new ClientException(_m('No valid URL provided for OpenID.')); + } $consumer = oid_consumer(); if (!$consumer) { // TRANS: OpenID plugin server error. - common_server_error(_m('Cannot instantiate OpenID consumer object.')); - return false; + throw new ServerException(_m('Cannot instantiate OpenID consumer object.')); } common_ensure_session(); @@ -148,12 +150,12 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) if (!$auth_request) { common_log(LOG_ERR, __METHOD__ . ": mystery fail contacting $openid_url"); // TRANS: OpenID plugin message. Given when an OpenID is not valid. - return _m('Not a valid OpenID.'); + throw new ServerException(_m('Not a valid OpenID.')); } else if (Auth_OpenID::isFailure($auth_request)) { common_log(LOG_ERR, __METHOD__ . ": OpenID fail to $openid_url: $auth_request->message"); // TRANS: OpenID plugin server error. Given when the OpenID authentication request fails. // TRANS: %s is the failure message. - return sprintf(_m('OpenID failure: %s.'), $auth_request->message); + throw new ServerException(sprintf(_m('OpenID failure: %s.'), $auth_request->message)); } $sreg_request = Auth_OpenID_SRegRequest::build(// Required @@ -199,14 +201,12 @@ function oid_authenticate($openid_url, $returnto, $immediate=false) $redirect_url = $auth_request->redirectURL($trust_root, $process_url, $immediate); - if (!$redirect_url) { - } else if (Auth_OpenID::isFailure($redirect_url)) { + if (Auth_OpenID::isFailure($redirect_url)) { // TRANS: OpenID plugin server error. Given when the OpenID authentication request cannot be redirected. // TRANS: %s is the failure message. - return sprintf(_m('Could not redirect to server: %s.'), $redirect_url->message); - } else { - common_redirect($redirect_url, 303); + throw new ServerException(sprintf(_m('Could not redirect to server: %s.'), $redirect_url->message)); } + common_redirect($redirect_url, 303); /* } else { // Generate form markup and render it. From 712a6d49d0cb9f8a6afffd7c2b163b429b0b2666 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 16:19:02 +0200 Subject: [PATCH 078/156] Poll settings adapted to FormAction --- plugins/Poll/actions/pollsettings.php | 129 ++------------------------ plugins/Poll/forms/pollprefs.php | 87 +++++++++++++++++ 2 files changed, 97 insertions(+), 119 deletions(-) create mode 100644 plugins/Poll/forms/pollprefs.php diff --git a/plugins/Poll/actions/pollsettings.php b/plugins/Poll/actions/pollsettings.php index b390812cf4..b95465d8d6 100644 --- a/plugins/Poll/actions/pollsettings.php +++ b/plugins/Poll/actions/pollsettings.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } class PollSettingsAction extends SettingsAction { @@ -56,143 +54,36 @@ class PollSettingsAction extends SettingsAction return _m('Set your poll preferences'); } - /** - * Show the form for Poll - * - * @return void - */ - function showContent() + protected function getForm() { - $user = common_current_user(); - - $prefs = User_poll_prefs::getKV('user_id', $user->id); - + $prefs = User_poll_prefs::getKV('user_id', $this->scoped->getID()); $form = new PollPrefsForm($this, $prefs); - - $form->show(); + return $form; } - /** - * Handler method - * - * @param array $argarray is ignored since it's now passed in in prepare() - * - * @return void - */ - - function handlePost() + protected function doPost() { - $user = common_current_user(); - - $upp = User_poll_prefs::getKV('user_id', $user->id); + $upp = User_poll_prefs::getKV('user_id', $this->scoped->getID()); $orig = null; - if (!empty($upp)) { + if ($upp instanceof User_poll_prefs) { $orig = clone($upp); } else { $upp = new User_poll_prefs(); - $upp->user_id = $user->id; + $upp->user_id = $this->scoped->getID(); $upp->created = common_sql_now(); } $upp->hide_responses = $this->boolean('hide_responses'); $upp->modified = common_sql_now(); - if (!empty($orig)) { + if ($orig instanceof User_poll_prefs) { $upp->update($orig); } else { $upp->insert(); } // TRANS: Confirmation shown when user profile settings are saved. - $this->showForm(_('Settings saved.'), true); - - return; - } -} - -class PollPrefsForm extends Form -{ - var $prefs; - - function __construct($out, $prefs) - { - parent::__construct($out); - $this->prefs = $prefs; - } - - /** - * Visible or invisible data elements - * - * Display the form fields that make up the data of the form. - * Sub-classes should overload this to show their data. - * - * @return void - */ - - function formData() - { - $this->elementStart('fieldset'); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->checkbox('hide_responses', - _('Do not deliver poll responses to my home timeline'), - (!empty($this->prefs) && $this->prefs->hide_responses)); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - } - - /** - * Buttons for form actions - * - * Submit and cancel buttons (or whatever) - * Sub-classes should overload this to show their own buttons. - * - * @return void - */ - - function formActions() - { - $this->submit('submit', _('Save')); - } - - /** - * ID of the form - * - * Should be unique on the page. Sub-classes should overload this - * to show their own IDs. - * - * @return int ID of the form - */ - - function id() - { - return 'form_poll_prefs'; - } - - /** - * Action of the form. - * - * URL to post to. Should be overloaded by subclasses to give - * somewhere to post to. - * - * @return string URL to post to - */ - - function action() - { - return common_local_url('pollsettings'); - } - - /** - * Class of the form. May include space-separated list of multiple classes. - * - * @return string the form's class - */ - - function formClass() - { - return 'form_settings'; + return _('Settings saved.'); } } diff --git a/plugins/Poll/forms/pollprefs.php b/plugins/Poll/forms/pollprefs.php new file mode 100644 index 0000000000..627b77d948 --- /dev/null +++ b/plugins/Poll/forms/pollprefs.php @@ -0,0 +1,87 @@ +prefs = $prefs; + } + + /** + * Visible or invisible data elements + * + * Display the form fields that make up the data of the form. + * Sub-classes should overload this to show their data. + * + * @return void + */ + + function formData() + { + $this->elementStart('fieldset'); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->checkbox('hide_responses', + _('Do not deliver poll responses to my home timeline'), + ($this->prefs instanceof User_poll_prefs && $this->prefs->hide_responses)); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->elementEnd('fieldset'); + } + + /** + * Buttons for form actions + * + * Submit and cancel buttons (or whatever) + * Sub-classes should overload this to show their own buttons. + * + * @return void + */ + + function formActions() + { + $this->submit('submit', _('Save')); + } + + /** + * ID of the form + * + * Should be unique on the page. Sub-classes should overload this + * to show their own IDs. + * + * @return int ID of the form + */ + + function id() + { + return 'form_poll_prefs'; + } + + /** + * Action of the form. + * + * URL to post to. Should be overloaded by subclasses to give + * somewhere to post to. + * + * @return string URL to post to + */ + + function action() + { + return common_local_url('pollsettings'); + } + + /** + * Class of the form. May include space-separated list of multiple classes. + * + * @return string the form's class + */ + + function formClass() + { + return 'form_settings'; + } +} From d6d06c8cbbb97cc5adf585ef49a0ac32e86bfef7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 17:09:36 +0200 Subject: [PATCH 079/156] SubMirror now works properly as extended from FormAction --- plugins/SubMirror/actions/mirrorsettings.php | 52 +++----------------- 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/plugins/SubMirror/actions/mirrorsettings.php b/plugins/SubMirror/actions/mirrorsettings.php index b5a49fe4fa..1bfc848ebb 100644 --- a/plugins/SubMirror/actions/mirrorsettings.php +++ b/plugins/SubMirror/actions/mirrorsettings.php @@ -25,7 +25,7 @@ * @link http://status.net/ */ -if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } class MirrorSettingsAction extends SettingsAction { @@ -62,9 +62,8 @@ class MirrorSettingsAction extends SettingsAction */ function showContent() { - $user = common_current_user(); $provider = $this->trimmed('provider'); - if ($provider) { + if (!empty($provider) || GNUsocial::isAjax()) { $this->showAddFeedForm($provider); } else { $this->elementStart('div', array('id' => 'add-mirror')); @@ -72,7 +71,7 @@ class MirrorSettingsAction extends SettingsAction $this->elementEnd('div'); $mirror = new SubMirror(); - $mirror->subscriber = $user->id; + $mirror->subscriber = $this->scoped->getID(); if ($mirror->find()) { while ($mirror->fetch()) { $this->showFeedForm($mirror); @@ -87,13 +86,11 @@ class MirrorSettingsAction extends SettingsAction $form->show(); } - function showFeedForm($mirror) + function showFeedForm(SubMirror $mirror) { - $profile = Profile::getKV('id', $mirror->subscribed); - if ($profile) { - $form = new EditMirrorForm($this, $profile); - $form->show(); - } + $profile = Profile::getByID($mirror->subscribed); + $form = new EditMirrorForm($this, $profile); + $form->show(); } function showAddFeedForm() @@ -112,41 +109,6 @@ class MirrorSettingsAction extends SettingsAction $form->show(); } - /** - * - * @param array $args - * - * @todo move the ajax display handling to common code - */ - function handle($args) - { - if ($this->boolean('ajax')) { - $this->startHTML('text/xml;charset=utf-8'); - $this->elementStart('head'); - // TRANS: Title for page with form to add a mirror feed provider on. - $this->element('title', null, _m('Provider add')); - $this->elementEnd('head'); - $this->elementStart('body'); - - $this->showAddFeedForm(); - - $this->elementEnd('body'); - $this->endHTML(); - } else { - return parent::handle($args); - } - } - /** - * Handle a POST request - * - * Muxes to different sub-functions based on which button was pushed - * - * @return void - */ - function handlePost() - { - } - /** * Show the local navigation menu * From 5933056a5b9490bfbe030843cc83c5e43084e56c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 17:50:43 +0200 Subject: [PATCH 080/156] Twittersettings now works better as Profilesettings extension --- .../TwitterBridge/actions/twittersettings.php | 100 +++++++----------- 1 file changed, 39 insertions(+), 61 deletions(-) diff --git a/plugins/TwitterBridge/actions/twittersettings.php b/plugins/TwitterBridge/actions/twittersettings.php index 37abb4d272..85d1a4bbe2 100644 --- a/plugins/TwitterBridge/actions/twittersettings.php +++ b/plugins/TwitterBridge/actions/twittersettings.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } require_once dirname(__DIR__) . '/twitter.php'; @@ -46,6 +44,16 @@ require_once dirname(__DIR__) . '/twitter.php'; */ class TwittersettingsAction extends ProfileSettingsAction { + protected $flink = null; + protected $fuser = null; + + protected function doPreparation() + { + $this->flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); + if ($this->flink instanceof Foreign_link) { + $this->fuser = $this->flink->getForeignUser(); + } + } /** * Title of the page * @@ -81,19 +89,6 @@ class TwittersettingsAction extends ProfileSettingsAction */ function showContent() { - - $user = common_current_user(); - - $profile = $user->getProfile(); - - $fuser = null; - - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - - if (!empty($flink)) { - $fuser = $flink->getForeignUser(); - } - $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_twitter', 'class' => 'form_settings', @@ -104,21 +99,11 @@ class TwittersettingsAction extends ProfileSettingsAction $this->elementStart('fieldset', array('id' => 'settings_twitter_account')); - if (empty($fuser)) { - $this->elementStart('ul', 'form_data'); - $this->elementStart('li', array('id' => 'settings_twitter_login_button')); - $this->element('a', array('href' => common_local_url('twitterauthorization')), - // TRANS: Link description to connect to a Twitter account. - 'Connect my Twitter account'); - $this->elementEnd('li'); - $this->elementEnd('ul'); - - $this->elementEnd('fieldset'); - } else { + if ($this->fuser instanceof Foreign_user) { // TRANS: Fieldset legend. $this->element('legend', null, _m('Twitter account')); $this->elementStart('p', array('id' => 'form_confirmed')); - $this->element('a', array('href' => $fuser->uri), $fuser->nickname); + $this->element('a', array('href' => $this->fuser->uri), $this->fuser->nickname); $this->elementEnd('p'); $this->element('p', 'form_note', // TRANS: Form note when a Twitter account has been connected. @@ -130,7 +115,7 @@ class TwittersettingsAction extends ProfileSettingsAction // TRANS: Fieldset legend. $this->element('legend', null, _m('Disconnect my account from Twitter')); - if (!$user->password) { + if (!$this->scoped->hasPassword()) { $this->elementStart('p', array('class' => 'form_guide')); // TRANS: Form guide. %s is a URL to the password settings. // TRANS: This message contains a Markdown link in the form [description](link). @@ -165,25 +150,19 @@ class TwittersettingsAction extends ProfileSettingsAction $this->checkbox('noticesend', // TRANS: Checkbox label. _m('Automatically send my notices to Twitter.'), - ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_SEND) : - true); + $this->flink->noticesync & FOREIGN_NOTICE_SEND); $this->elementEnd('li'); $this->elementStart('li'); $this->checkbox('replysync', // TRANS: Checkbox label. _m('Send local "@" replies to Twitter.'), - ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : - true); + $this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY); $this->elementEnd('li'); $this->elementStart('li'); $this->checkbox('friendsync', // TRANS: Checkbox label. _m('Subscribe to my Twitter friends here.'), - ($flink) ? - ($flink->friendsync & FOREIGN_FRIEND_RECV) : - false); + $this->flink->friendsync & FOREIGN_FRIEND_RECV); $this->elementEnd('li'); if (common_config('twitterimport','enabled')) { @@ -191,31 +170,37 @@ class TwittersettingsAction extends ProfileSettingsAction $this->checkbox('noticerecv', // TRANS: Checkbox label. _m('Import my friends timeline.'), - ($flink) ? - ($flink->noticesync & FOREIGN_NOTICE_RECV) : - false); + $this->flink->noticesync & FOREIGN_NOTICE_RECV); $this->elementEnd('li'); } else { // preserve setting even if bidrection bridge toggled off - if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) { + if ($this->flink->noticesync & FOREIGN_NOTICE_RECV) { $this->hidden('noticerecv', true, 'noticerecv'); } } $this->elementEnd('ul'); - if ($flink) { + if ($this->flink) { // TRANS: Button text for saving Twitter integration settings. $this->submit('save', _m('BUTTON','Save')); } else { // TRANS: Button text for adding Twitter integration. $this->submit('add', _m('BUTTON','Add')); } - - $this->elementEnd('fieldset'); + } else { + $this->elementStart('ul', 'form_data'); + $this->elementStart('li', array('id' => 'settings_twitter_login_button')); + $this->element('a', array('href' => common_local_url('twitterauthorization')), + // TRANS: Link description to connect to a Twitter account. + 'Connect my Twitter account'); + $this->elementEnd('li'); + $this->elementEnd('ul'); } + $this->elementEnd('fieldset'); + $this->elementEnd('form'); } @@ -289,32 +274,25 @@ class TwittersettingsAction extends ProfileSettingsAction $friendsync = $this->boolean('friendsync'); $replysync = $this->boolean('replysync'); - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - - if (empty($flink)) { - common_log_db_error($flink, 'SELECT', __FILE__); - // @todo FIXME: Shouldn't this be a serverError()? + if (!$this->flink instanceof Foreign_link) { + common_log_db_error($this->flink, 'SELECT', __FILE__); // TRANS: Server error displayed when saving Twitter integration preferences fails. - $this->showForm(_m('Could not save Twitter preferences.')); - return; + throw new ServerException(_m('Your account is not linked to Twitter.')); } - $original = clone($flink); + $original = clone($this->flink); $wasReceiving = (bool)($original->noticesync & FOREIGN_NOTICE_RECV); - $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync); - $result = $flink->update($original); + $this->flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync); + $result = $this->flink->update($original); if ($result === false) { - common_log_db_error($flink, 'UPDATE', __FILE__); - // @todo FIXME: Shouldn't this be a serverError()? + common_log_db_error($this->flink, 'UPDATE', __FILE__); // TRANS: Server error displayed when saving Twitter integration preferences fails. - $this->showForm(_m('Could not save Twitter preferences.')); - return; + throw new ServerException(_m('Could not save Twitter preferences.')); } if ($wasReceiving xor $noticerecv) { - $this->notifyDaemon($flink->foreign_id, $noticerecv); + $this->notifyDaemon($this->flink->foreign_id, $noticerecv); } // TRANS: Success message after saving Twitter integration preferences. From 6cd7a4a400e87e6a0ff1ddb2a2ba0a26c5df9b67 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 18:44:09 +0200 Subject: [PATCH 081/156] TwitterBridge messing about, Twitter OAuth requires Authorization header now? https://dev.twitter.com/oauth/reference/post/oauth/request_token says that the request should be a GET with a specific HTTP header instead of query string parameters for OAuth? --- classes/Foreign_link.php | 6 +- classes/Foreign_user.php | 31 +-- plugins/FacebookBridge/lib/facebookclient.php | 11 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 18 +- .../actions/twitterauthorization.php | 229 ++++++------------ .../TwitterBridge/actions/twitterlogin.php | 27 +-- .../TwitterBridge/lib/twitteroauthclient.php | 4 +- plugins/TwitterBridge/twitter.php | 31 +-- 8 files changed, 131 insertions(+), 226 deletions(-) diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 6176ec43bc..0d942443f3 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -124,11 +124,11 @@ class Foreign_link extends Managed_DataObject $fuser->limit(1); - if ($fuser->find(true)) { - return $fuser; + if (!$fuser->find(true)) { + throw new NoResultException($fuser); } - return null; + return $fuser; } function getUser() diff --git a/classes/Foreign_user.php b/classes/Foreign_user.php index c1739d318a..3d23eabef9 100644 --- a/classes/Foreign_user.php +++ b/classes/Foreign_user.php @@ -42,32 +42,33 @@ class Foreign_user extends Managed_DataObject } static function getForeignUser($id, $service) { - $fuser = new Foreign_user(); - $fuser->id = $id; $fuser->service = $service; - $fuser->limit(1); - $result = $fuser->find(true); + if (!$fuser->find(true)) { + throw new NoResultException($fuser); + } - return empty($result) ? null : $fuser; + return $fuser; } static function getByNickname($nickname, $service) { if (empty($nickname) || empty($service)) { - return null; - } else { - $fuser = new Foreign_user(); - $fuser->service = $service; - $fuser->nickname = $nickname; - $fuser->limit(1); - - $result = $fuser->find(true); - - return empty($result) ? null : $fuser; + throw new ServerException('Empty nickname or service for Foreign_user::getByNickname'); } + + $fuser = new Foreign_user(); + $fuser->service = $service; + $fuser->nickname = $nickname; + $fuser->limit(1); + + if (!$fuser->find(true)) { + throw new NoResultException($fuser); + } + + return $fuser; } } diff --git a/plugins/FacebookBridge/lib/facebookclient.php b/plugins/FacebookBridge/lib/facebookclient.php index 5b512dfdff..e55e19d88b 100644 --- a/plugins/FacebookBridge/lib/facebookclient.php +++ b/plugins/FacebookBridge/lib/facebookclient.php @@ -918,12 +918,9 @@ class Facebookclient static function addFacebookUser($fbuser) { // remove any existing, possibly outdated, record - $luser = Foreign_user::getForeignUser($fbuser->id, FACEBOOK_SERVICE); - - if (!empty($luser)) { - - $result = $luser->delete(); - + try { + $fuser = Foreign_user::getForeignUser($fbuser->id, FACEBOOK_SERVICE); + $result = $fuser->delete(); if ($result != false) { common_log( LOG_INFO, @@ -935,6 +932,8 @@ class Facebookclient __FILE__ ); } + } catch (NoResultException $e) { + // no old foreign users exist for this id } $fuser = new Foreign_user(); diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 9b129ea21b..566038f2ec 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -26,9 +26,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } require_once __DIR__ . '/twitter.php'; @@ -387,12 +385,11 @@ class TwitterBridgePlugin extends Plugin { $n2s = Notice_to_status::getKV('notice_id', $notice->id); - if (!empty($n2s)) { + if ($n2s instanceof Notice_to_status) { - $flink = Foreign_link::getByUserID($notice->profile_id, - TWITTER_SERVICE); // twitter service + $flink = Foreign_link::getByUserID($notice->profile_id, TWITTER_SERVICE); // twitter service - if (empty($flink)) { + if (!$flink instanceof Foreign_link) { return true; } @@ -424,15 +421,14 @@ class TwitterBridgePlugin extends Plugin */ function onEndFavorNotice(Profile $profile, Notice $notice) { - $flink = Foreign_link::getByUserID($profile->id, - TWITTER_SERVICE); // twitter service + $flink = Foreign_link::getByUserID($profile->getID(), TWITTER_SERVICE); // twitter service - if (empty($flink)) { + if (!$flink instanceof Foreign_link) { return true; } if (!TwitterOAuthClient::isPackedToken($flink->credentials)) { - $this->log(LOG_INFO, "Skipping fave processing for {$profile->id} since link is not OAuth."); + $this->log(LOG_INFO, "Skipping fave processing for {$profile->getID()} since link is not OAuth."); return true; } diff --git a/plugins/TwitterBridge/actions/twitterauthorization.php b/plugins/TwitterBridge/actions/twitterauthorization.php index ae293edcf7..90e4c35410 100644 --- a/plugins/TwitterBridge/actions/twitterauthorization.php +++ b/plugins/TwitterBridge/actions/twitterauthorization.php @@ -28,7 +28,7 @@ * @link http://status.net/ */ -if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); } +if (!defined('GNUSOCIAL')) { exit(1); } require_once dirname(__DIR__) . '/twitter.php'; @@ -48,7 +48,7 @@ require_once dirname(__DIR__) . '/twitter.php'; * @link http://status.net/ * */ -class TwitterauthorizationAction extends Action +class TwitterauthorizationAction extends FormAction { var $twuid = null; var $tw_fields = null; @@ -56,112 +56,80 @@ class TwitterauthorizationAction extends Action var $signin = null; var $verifier = null; - /** - * Initialize class members. Looks for 'oauth_token' parameter. - * - * @param array $args misc. arguments - * - * @return boolean true - */ - function prepare($args) - { - parent::prepare($args); + protected $needLogin = false; // authorization page can also be used to create a new user + protected function doPreparation() + { $this->signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); $this->verifier = $this->arg('oauth_verifier'); - return true; - } - - /** - * Handler method - * - * @param array $args is ignored since it's now passed in in prepare() - * - * @return nothing - */ - function handle($args) - { - parent::handle($args); - - if (common_logged_in()) { - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); + if ($this->scoped instanceof Profile) { + $flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); // If there's already a foreign link record and a foreign user // it means the accounts are already linked, and this is unecessary. // So go back. - if (isset($flink)) { - $fuser = $flink->getForeignUser(); - if (!empty($fuser)) { + if ($flink instanceof Foreign_link) { + try { + $fuser = $flink->getForeignUser(); common_redirect(common_local_url('twittersettings')); + } catch (NoResultException $e) { + // ok, so no foreign user but there's a foreign link?? + // this logic is left since the StatusNet days } } } - if ($_SERVER['REQUEST_METHOD'] == 'POST') { - - // User was not logged in to StatusNet before - - $this->twuid = $this->trimmed('twuid'); - - $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), - 'fullname' => $this->trimmed('tw_fields_fullname')); - - $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); - - $token = $this->trimmed('token'); - - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_m('There was a problem with your session token. Try again, please.')); - return; - } - - if ($this->arg('create')) { - if (!$this->boolean('license')) { - // TRANS: Form validation error displayed when the checkbox to agree to the license has not been checked. - $this->showForm(_m('You cannot register if you do not agree to the license.'), - $this->trimmed('newname')); - return; - } - $this->createNewUser(); - } else if ($this->arg('connect')) { - $this->connectNewUser(); - } else { - common_debug('Twitter bridge - ' . print_r($this->args, true)); - // TRANS: Form validation error displayed when an unhandled error occurs. - $this->showForm(_m('Something weird happened.'), - $this->trimmed('newname')); - } + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + if (empty($this->oauth_token)) { + // authorizeRequestToken either throws an exception or redirects + $this->authorizeRequestToken(); } else { - // $this->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process - - if (empty($this->oauth_token)) { - $this->authorizeRequestToken(); - } else { - $this->saveAccessToken(); - } + $this->saveAccessToken(); } } + protected function doPost() + { + // User was not logged in to StatusNet before + + $this->twuid = $this->trimmed('twuid'); + + $this->tw_fields = array('screen_name' => $this->trimmed('tw_fields_screen_name'), + 'fullname' => $this->trimmed('tw_fields_fullname')); + + $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); + + if ($this->arg('create')) { + if (!$this->boolean('license')) { + // TRANS: Form validation error displayed when the checkbox to agree to the license has not been checked. + throw new ClientException(_m('You cannot register if you do not agree to the license.')); + } + return $this->createNewUser(); + } else if ($this->arg('connect')) { + return $this->connectNewUser(); + } + + common_debug('Twitter bridge - ' . print_r($this->args, true)); + // TRANS: Form validation error displayed when an unhandled error occurs. + throw new ClientException(_m('No known action for POST.')); + } + /** * Asks Twitter for a request token, and then redirects to Twitter * to authorize it. - * - * @return nothing */ - function authorizeRequestToken() + protected function authorizeRequestToken() { try { // Get a new request token and authorize it $client = new TwitterOAuthClient(); - $req_tok = $client->getRequestToken(); + $req_tok = $client->getTwitterRequestToken(); // Sock the request token away in the session temporarily @@ -176,10 +144,8 @@ class TwitterauthorizationAction extends Action $e->getMessage() ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); - $this->serverError( - // TRANS: Server error displayed when linking to a Twitter account fails. - _m('Could not link your Twitter account.') - ); + // TRANS: Server error displayed when linking to a Twitter account fails. + throw new ServerException(_m('Could not link your Twitter account.')); } common_redirect($auth_link); @@ -197,10 +163,8 @@ class TwitterauthorizationAction extends Action // token we sent them if ($_SESSION['twitter_request_token'] != $this->oauth_token) { - $this->serverError( - // TRANS: Server error displayed when linking to a Twitter account fails because of an incorrect oauth_token. - _m('Could not link your Twitter account: oauth_token mismatch.') - ); + // TRANS: Server error displayed when linking to a Twitter account fails because of an incorrect oauth_token. + throw new ServerException(_m('Could not link your Twitter account: oauth_token mismatch.')); } $twitter_user = null; @@ -212,7 +176,7 @@ class TwitterauthorizationAction extends Action // Exchange the request token for an access token - $atok = $client->getAccessToken($this->verifier); + $atok = $client->getTwitterAccessToken($this->verifier); // Test the access token and get the user's Twitter info @@ -235,8 +199,7 @@ class TwitterauthorizationAction extends Action if (common_logged_in()) { // Save the access token and Twitter user info - $user = common_current_user(); - $this->saveForeignLink($user->id, $twitter_user->id, $atok); + $this->saveForeignLink($this->scoped->getID(), $twitter_user->id, $atok); save_twitter_user($twitter_user->id, $twitter_user->screen_name); } else { @@ -297,24 +260,20 @@ class TwitterauthorizationAction extends Action $flink_id = $flink->insert(); + // We want to make sure we got a numerical >0 value, not just failed the insert (which would be === false) if (empty($flink_id)) { common_log_db_error($flink, 'INSERT', __FILE__); // TRANS: Server error displayed when linking to a Twitter account fails. - $this->serverError(_m('Could not link your Twitter account.')); + throw new ServerException(_m('Could not link your Twitter account.')); } return $flink_id; } - function showPageNotice() + function getInstructions() { - if ($this->error) { - $this->element('div', array('class' => 'error'), $this->error); - } else { - $this->element('div', 'instructions', - // TRANS: Page instruction. %s is the StatusNet sitename. - sprintf(_m('This is the first time you have logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); - } + // TRANS: Page instruction. %s is the StatusNet sitename. + return sprintf(_m('This is the first time you have logged into %s so we must connect your Twitter account to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')); } function title() @@ -323,19 +282,6 @@ class TwitterauthorizationAction extends Action return _m('Twitter Account Setup'); } - function showForm($error=null, $username=null) - { - $this->error = $error; - $this->username = $username; - - $this->showPage(); - } - - function showPage() - { - parent::showPage(); - } - /** * @fixme much of this duplicates core code, which is very fragile. * Should probably be replaced with an extensible mini version of @@ -380,7 +326,7 @@ class TwitterauthorizationAction extends Action $this->elementStart('li'); // TRANS: Field label. $this->input('newname', _m('New nickname'), - ($this->username) ? $this->username : '', + $this->username ?: '', // TRANS: Field title for nickname field. _m('1-64 lowercase letters or numbers, no punctuation or spaces.')); $this->elementEnd('li'); @@ -488,12 +434,13 @@ class TwitterauthorizationAction extends Action function createNewUser() { if (!Event::handle('StartRegistrationTry', array($this))) { - return; + // TRANS: Client error displayed when trying to create a new user but a plugin aborted the process. + throw new ClientException(_m('Registration of new user was aborted, maybe you failed a captcha?')); } if (common_config('site', 'closed')) { // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed. - $this->clientError(_m('Registration not allowed.')); + throw new ClientException(_m('Registration not allowed.')); } $invite = null; @@ -502,23 +449,19 @@ class TwitterauthorizationAction extends Action $code = $_SESSION['invitecode']; if (empty($code)) { // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed. - $this->clientError(_m('Registration not allowed.')); + throw new ClientException(_m('Registration not allowed.')); } - $invite = Invitation::getKV($code); + $invite = Invitation::getKV('code', $code); - if (empty($invite)) { + if (!$invite instanceof Invite) { // TRANS: Client error displayed when trying to create a new user with an invalid invitation code. - $this->clientError(_m('Not a valid invitation code.')); + throw new ClientException(_m('Not a valid invitation code.')); } } - try { - $nickname = Nickname::normalize($this->trimmed('newname'), true); - } catch (NicknameException $e) { - $this->showForm($e->getMessage()); - return; - } + // Nickname::normalize throws exception if the nickname is taken + $nickname = Nickname::normalize($this->trimmed('newname'), true); $fullname = trim($this->tw_fields['fullname']); @@ -533,23 +476,14 @@ class TwitterauthorizationAction extends Action $args['email'] = $email; } - try { - $user = User::register($args); - } catch (Exception $e) { - $this->serverError($e->getMessage()); - } + $user = User::register($args); - $result = $this->saveForeignLink($user->id, - $this->twuid, - $this->access_token); + $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); save_twitter_user($this->twuid, $this->tw_fields['screen_name']); - if (!$result) { - // TRANS: Server error displayed when connecting a user to a Twitter user has failed. - $this->serverError(_m('Error connecting user to Twitter.')); - } - common_set_user($user); common_real_login(true); @@ -569,28 +503,23 @@ class TwitterauthorizationAction extends Action if (!common_check_user($nickname, $password)) { // TRANS: Form validation error displayed when connecting an existing user to a Twitter user fails because // TRANS: the provided username and/or password are incorrect. - $this->showForm(_m('Invalid username or password.')); - return; + throw new ClientException(_m('Invalid username or password.')); } $user = User::getKV('nickname', $nickname); - if (!empty($user)) { + if ($user instanceof User) { common_debug('TwitterBridge Plugin - ' . "Legit user to connect to Twitter: $nickname"); } - $result = $this->saveForeignLink($user->id, - $this->twuid, - $this->access_token); + // throws exception on failure + $this->saveForeignLink($user->id, + $this->twuid, + $this->access_token); save_twitter_user($this->twuid, $this->tw_fields['screen_name']); - if (!$result) { - // TRANS: Server error displayed connecting a user to a Twitter user has failed. - $this->serverError(_m('Error connecting user to Twitter.')); - } - common_debug('TwitterBridge Plugin - ' . "Connected Twitter user $this->twuid to local user $user->id"); diff --git a/plugins/TwitterBridge/actions/twitterlogin.php b/plugins/TwitterBridge/actions/twitterlogin.php index ee00714c9f..f4af7d577d 100644 --- a/plugins/TwitterBridge/actions/twitterlogin.php +++ b/plugins/TwitterBridge/actions/twitterlogin.php @@ -28,9 +28,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } require_once dirname(__DIR__) . '/twitter.php'; @@ -46,20 +44,8 @@ require_once dirname(__DIR__) . '/twitter.php'; * * @see SettingsAction */ -class TwitterloginAction extends Action +class TwitterloginAction extends LoginAction { - function handle($args) - { - parent::handle($args); - - if (common_is_real_login()) { - // TRANS: Client error displayed when trying to log in using Twitter while already logged in to StatusNet. - $this->clientError(_m('Already logged in.')); - } - - $this->showPage(); - } - function title() { // TRANS: Title for login using Twitter page. @@ -72,15 +58,6 @@ class TwitterloginAction extends Action return _m('Login with your Twitter account'); } - function showPageNotice() - { - $instr = $this->getInstructions(); - $output = common_markup_to_html($instr); - $this->elementStart('div', 'instructions'); - $this->raw($output); - $this->elementEnd('div'); - } - function showContent() { $this->elementStart('a', array('href' => common_local_url('twitterauthorization', diff --git a/plugins/TwitterBridge/lib/twitteroauthclient.php b/plugins/TwitterBridge/lib/twitteroauthclient.php index f992a09079..9d3b244724 100644 --- a/plugins/TwitterBridge/lib/twitteroauthclient.php +++ b/plugins/TwitterBridge/lib/twitteroauthclient.php @@ -111,7 +111,7 @@ class TwitterOAuthClient extends OAuthClient * * @return OAuthToken $token the request token */ - function getRequestToken() + function getTwitterRequestToken() { return parent::getRequestToken( self::$requestTokenURL, @@ -142,7 +142,7 @@ class TwitterOAuthClient extends OAuthClient * * @return OAuthToken $token the access token */ - function getAccessToken($verifier = null) + function getTwitterAccessToken($verifier = null) { return parent::getAccessToken( self::$accessTokenURL, diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index b607fe6052..bdcd61db45 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -28,16 +28,17 @@ function add_twitter_user($twitter_id, $screen_name) // Clear out any bad old foreign_users with the new user's legit URL // This can happen when users move around or fakester accounts get // repoed, and things like that. - $luser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE); - - if (!empty($luser)) { - $result = $luser->delete(); + try { + $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE); + $result = $fuser->delete(); if ($result != false) { common_log( LOG_INFO, "Twitter bridge - removed old Twitter user: $screen_name ($twitter_id)." ); } + } catch (NoResultException $e) { + // no old foreign users exist for this id } $fuser = new Foreign_user(); @@ -49,9 +50,8 @@ function add_twitter_user($twitter_id, $screen_name) $fuser->created = common_sql_now(); $result = $fuser->insert(); - if (empty($result)) { - common_log(LOG_WARNING, - "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name."); + if ($result === false) { + common_log(LOG_WARNING, "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name."); common_log_db_error($fuser, 'INSERT', __FILE__); } else { common_log(LOG_INFO, @@ -66,11 +66,10 @@ function save_twitter_user($twitter_id, $screen_name) { // Check to see whether the Twitter user is already in the system, // and update its screen name and uri if so. - $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE); + try { + $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE); - if (!empty($fuser)) { // Delete old record if Twitter user changed screen name - if ($fuser->nickname != $screen_name) { $oldname = $fuser->nickname; $fuser->delete(); @@ -80,11 +79,13 @@ function save_twitter_user($twitter_id, $screen_name) $screen_name, $oldname)); } - } else { + } catch (NoResultException $e) { + // No old users exist for this id + // Kill any old, invalid records for this screen name - $fuser = Foreign_user::getByNickname($screen_name, TWITTER_SERVICE); - - if (!empty($fuser)) { + // XXX: Is this really only supposed to be run if the above getForeignUser fails? + try { + $fuser = Foreign_user::getByNickname($screen_name, TWITTER_SERVICE); $fuser->delete(); common_log( LOG_INFO, @@ -95,6 +96,8 @@ function save_twitter_user($twitter_id, $screen_name) $fuser->id ) ); + } catch (NoResultException $e) { + // No old users exist for this screen_name } } From e10d081a56bf600b1babc1fa1c774dc8cbe874c1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 17 Jul 2015 21:03:37 +0200 Subject: [PATCH 082/156] TwitterBridge is closer to working again --- classes/Foreign_link.php | 31 +++++---- classes/Foreign_user.php | 7 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 15 ++-- .../actions/twitterauthorization.php | 69 ++++++++----------- .../TwitterBridge/actions/twittersettings.php | 6 +- 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 0d942443f3..6e050d0c94 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -56,34 +56,37 @@ class Foreign_link extends Managed_DataObject static function getByUserID($user_id, $service) { if (empty($user_id) || empty($service)) { - return null; + throw new ServerException('Empty user_id or service for Foreign_link::getByUserID'); } $flink = new Foreign_link(); - $flink->service = $service; $flink->user_id = $user_id; $flink->limit(1); - $result = $flink->find(true); + if (!$flink->find(true)) { + throw new NoResultException($flink); + } - return empty($result) ? null : $flink; + return $flink; } static function getByForeignID($foreign_id, $service) { if (empty($foreign_id) || empty($service)) { - return null; - } else { - $flink = new Foreign_link(); - $flink->service = $service; - $flink->foreign_id = $foreign_id; - $flink->limit(1); - - $result = $flink->find(true); - - return empty($result) ? null : $flink; + throw new ServerException('Empty foreign_id or service for Foreign_link::getByForeignID'); } + + $flink = new Foreign_link(); + $flink->service = $service; + $flink->foreign_id = $foreign_id; + $flink->limit(1); + + if (!$flink->find(true)) { + throw new NoResultException($flink); + } + + return $flink; } function set_flags($noticesend, $noticerecv, $replysync, $friendsync) diff --git a/classes/Foreign_user.php b/classes/Foreign_user.php index 3d23eabef9..1f6c77851d 100644 --- a/classes/Foreign_user.php +++ b/classes/Foreign_user.php @@ -41,7 +41,12 @@ class Foreign_user extends Managed_DataObject ); } - static function getForeignUser($id, $service) { + static function getForeignUser($id, $service) + { + if (empty($id) || empty($service)) { + throw new ServerException('Empty foreign user id or service for Foreign_user::getForeignUser'); + } + $fuser = new Foreign_user(); $fuser->id = $id; $fuser->service = $service; diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 566038f2ec..84c6285029 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -512,16 +512,15 @@ class TwitterBridgePlugin extends Plugin { $fuser = null; - $flink = Foreign_link::getByUserID($profile->id, TWITTER_SERVICE); - - if (!empty($flink)) { + try { + $flink = Foreign_link::getByUserID($profile->id, TWITTER_SERVICE); $fuser = $flink->getForeignUser(); - if (!empty($fuser)) { - $links[] = array("href" => $fuser->uri, - "text" => sprintf(_("@%s on Twitter"), $fuser->nickname), - "image" => $this->path("icons/twitter-bird-white-on-blue.png")); - } + $links[] = array("href" => $fuser->uri, + "text" => sprintf(_("@%s on Twitter"), $fuser->nickname), + "image" => $this->path("icons/twitter-bird-white-on-blue.png")); + } catch (NoResultException $e) { + // no foreign link for Twitter on this profile ID } return true; diff --git a/plugins/TwitterBridge/actions/twitterauthorization.php b/plugins/TwitterBridge/actions/twitterauthorization.php index 90e4c35410..43a46835c3 100644 --- a/plugins/TwitterBridge/actions/twitterauthorization.php +++ b/plugins/TwitterBridge/actions/twitterauthorization.php @@ -31,6 +31,7 @@ if (!defined('GNUSOCIAL')) { exit(1); } require_once dirname(__DIR__) . '/twitter.php'; +require_once INSTALLDIR . '/lib/oauthclient.php'; /** * Class for doing OAuth authentication against Twitter @@ -81,16 +82,6 @@ class TwitterauthorizationAction extends FormAction } } } - - // $this->oauth_token is only populated once Twitter authorizes our - // request token. If it's empty we're at the beginning of the auth - // process - if (empty($this->oauth_token)) { - // authorizeRequestToken either throws an exception or redirects - $this->authorizeRequestToken(); - } else { - $this->saveAccessToken(); - } } protected function doPost() @@ -127,12 +118,10 @@ class TwitterauthorizationAction extends FormAction { try { // Get a new request token and authorize it - $client = new TwitterOAuthClient(); $req_tok = $client->getTwitterRequestToken(); // Sock the request token away in the session temporarily - $_SESSION['twitter_request_token'] = $req_tok->key; $_SESSION['twitter_request_token_secret'] = $req_tok->secret; @@ -170,16 +159,12 @@ class TwitterauthorizationAction extends FormAction $twitter_user = null; try { - - $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], - $_SESSION['twitter_request_token_secret']); + $client = new TwitterOAuthClient($_SESSION['twitter_request_token'], $_SESSION['twitter_request_token_secret']); // Exchange the request token for an access token - $atok = $client->getTwitterAccessToken($this->verifier); // Test the access token and get the user's Twitter info - $client = new TwitterOAuthClient($atok->key, $atok->secret); $twitter_user = $client->verifyCredentials(); @@ -190,13 +175,11 @@ class TwitterauthorizationAction extends FormAction $e->getMessage() ); common_log(LOG_INFO, 'Twitter bridge - ' . $msg); - $this->serverError( - // TRANS: Server error displayed when linking to a Twitter account fails. - _m('Could not link your Twitter account.') - ); + // TRANS: Server error displayed when linking to a Twitter account fails. + throw new ServerException(_m('Could not link your Twitter account.')); } - if (common_logged_in()) { + if ($this->scoped instanceof Profile) { // Save the access token and Twitter user info $this->saveForeignLink($this->scoped->getID(), $twitter_user->id, $atok); @@ -208,7 +191,7 @@ class TwitterauthorizationAction extends FormAction $this->tw_fields = array("screen_name" => $twitter_user->screen_name, "fullname" => $twitter_user->name); $this->access_token = $atok; - $this->tryLogin(); + return $this->tryLogin(); } // Clean up the the mess we made in the session @@ -282,6 +265,21 @@ class TwitterauthorizationAction extends FormAction return _m('Twitter Account Setup'); } + public function showPage() + { + // $this->oauth_token is only populated once Twitter authorizes our + // request token. If it's empty we're at the beginning of the auth + // process + if (empty($this->oauth_token)) { + // authorizeRequestToken either throws an exception or redirects + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } + + parent::showPage(); + } + /** * @fixme much of this duplicates core code, which is very fragile. * Should probably be replaced with an extensible mini version of @@ -289,11 +287,6 @@ class TwitterauthorizationAction extends FormAction */ function showContent() { - if (!empty($this->message_text)) { - $this->element('p', null, $this->message); - return; - } - $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_twitter_connect', 'class' => 'form_settings', @@ -310,7 +303,7 @@ class TwitterauthorizationAction extends FormAction $this->hidden('token', common_session_token()); // Don't allow new account creation if site is flagged as invite only - if (common_config('site', 'inviteonly') == false) { + if (common_config('site', 'inviteonly') == false) { $this->elementStart('fieldset'); $this->element('legend', null, // TRANS: Fieldset legend. @@ -425,12 +418,6 @@ class TwitterauthorizationAction extends FormAction return ''; } - function message($msg) - { - $this->message_text = $msg; - $this->showPage(); - } - function createNewUser() { if (!Event::handle('StartRegistrationTry', array($this))) { @@ -567,14 +554,12 @@ class TwitterauthorizationAction extends FormAction common_real_login(true); $this->goHome($user->nickname); } - - } else { - - common_debug('TwitterBridge Plugin - ' . - "No flink found for twuid: $this->twuid - new user"); - - $this->showForm(null, $this->bestNewNickname()); } + common_debug('TwitterBridge Plugin - ' . + "No flink found for twuid: $this->twuid - new user"); + + return; + throw new ServerException(_m('No foreign link found for Twitter user')); } function goHome($nickname) diff --git a/plugins/TwitterBridge/actions/twittersettings.php b/plugins/TwitterBridge/actions/twittersettings.php index 85d1a4bbe2..efde36797c 100644 --- a/plugins/TwitterBridge/actions/twittersettings.php +++ b/plugins/TwitterBridge/actions/twittersettings.php @@ -49,9 +49,11 @@ class TwittersettingsAction extends ProfileSettingsAction protected function doPreparation() { - $this->flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); - if ($this->flink instanceof Foreign_link) { + try { + $this->flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); $this->fuser = $this->flink->getForeignUser(); + } catch (NoResultException $e) { + // No foreign link found for this user! } } /** From beba2a25d0e1f89236702a9a2c739e4ca521e0de Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 01:09:50 +0200 Subject: [PATCH 083/156] Don't retry unhandled transports in OpportunisticQM It'd continue trying xmpp transports forever, for example... --- classes/Queue_item.php | 7 ++++++- lib/dbqueuemanager.php | 2 +- lib/queuemanager.php | 12 ++++++++++++ .../lib/opportunisticqueuemanager.php | 9 ++++++++- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/classes/Queue_item.php b/classes/Queue_item.php index 3a7d05adef..d41c53e0e0 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -40,7 +40,7 @@ class Queue_item extends Managed_DataObject * @param mixed $transports name of a single queue or array of queues to pull from * If not specified, checks all queues in the system. */ - static function top($transports=null) { + static function top($transports=null, array $ignored_transports=array()) { $qi = new Queue_item(); if ($transports) { @@ -52,6 +52,11 @@ class Queue_item extends Managed_DataObject $qi->transport = $transports; } } + if (!empty($ignored_transports)) { + // @fixme use safer escaping + $list = implode("','", array_map(array($qi, 'escape'), $ignored_transports)); + $qi->whereAdd("transport NOT IN ('$list')"); + } $qi->orderBy('created'); $qi->whereAdd('claimed is null'); diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 45c4b694d2..9fb77eed96 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -72,7 +72,7 @@ class DBQueueManager extends QueueManager public function poll() { //$this->_log(LOG_DEBUG, 'Checking for notices...'); - $qi = Queue_item::top($this->activeQueues()); + $qi = Queue_item::top($this->activeQueues(), $this->getIgnoredTransports()); if (!$qi instanceof Queue_item) { //$this->_log(LOG_DEBUG, 'No notices waiting; idling.'); return false; diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 45fe1e4ab4..487104099a 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -43,6 +43,7 @@ abstract class QueueManager extends IoManager protected $handlers = array(); protected $groups = array(); protected $activeGroups = array(); + protected $ignoredTransports = array(); /** * Factory function to pull the appropriate QueueManager object @@ -255,6 +256,17 @@ abstract class QueueManager extends IoManager return array_keys($queues); } + function getIgnoredTransports() + { + return array_keys($this->ignoredTransports); + } + + function ignoreTransport($transport) + { + // key is used for uniqueness, value doesn't mean anything + $this->ignoredTransports[$transport] = true; + } + /** * Initialize the list of queue handlers for the current site. * diff --git a/plugins/OpportunisticQM/lib/opportunisticqueuemanager.php b/plugins/OpportunisticQM/lib/opportunisticqueuemanager.php index 4b2b679b58..b2dc61e15f 100644 --- a/plugins/OpportunisticQM/lib/opportunisticqueuemanager.php +++ b/plugins/OpportunisticQM/lib/opportunisticqueuemanager.php @@ -83,10 +83,17 @@ class OpportunisticQueueManager extends DBQueueManager // OpportunisticQM shouldn't discard items it can't handle, we're // only here to take care of what we _can_ handle! protected function noHandlerFound(Queue_item $qi, $rep=null) { - $this->_log(LOG_WARNING, "[{$qi->transport}:item {$qi->id}] Releasing claim for queue item without a handler"); + $this->_log(LOG_WARNING, "[{$qi->transport}:item {$qi->id}] Releasing claim for queue item without a handler"); $this->_fail($qi, true); // true here means "releaseOnly", so no error statistics since it's not an _error_ } + protected function _fail(Queue_item $qi, $releaseOnly=false) + { + parent::_fail($qi, $releaseOnly); + $this->_log(LOG_DEBUG, "[{$qi->transport}:item {$qi->id}] Ignoring this transport for the rest of this execution"); + $this->ignoreTransport($qi->transport); + } + /** * Takes care of running through the queue items, returning when * the limits setup in __construct are met. From 9fdf6474f8f466f8323bf8591b0d5037d926acc1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 01:18:08 +0200 Subject: [PATCH 084/156] TwitterBridge working again (for signin and posting to Twitter at least) Now we just need to make it include the newname suggestion if the form fails (for example forgetting to check the license checkbox or so). --- .../actions/twitterauthorization.php | 77 +++++++++++-------- .../TwitterBridge/actions/twittersettings.php | 48 +++++------- .../TwitterBridge/lib/twitteroauthclient.php | 2 +- 3 files changed, 63 insertions(+), 64 deletions(-) diff --git a/plugins/TwitterBridge/actions/twitterauthorization.php b/plugins/TwitterBridge/actions/twitterauthorization.php index 43a46835c3..3eb302bda4 100644 --- a/plugins/TwitterBridge/actions/twitterauthorization.php +++ b/plugins/TwitterBridge/actions/twitterauthorization.php @@ -54,32 +54,28 @@ class TwitterauthorizationAction extends FormAction var $twuid = null; var $tw_fields = null; var $access_token = null; - var $signin = null; var $verifier = null; protected $needLogin = false; // authorization page can also be used to create a new user protected function doPreparation() { - $this->signin = $this->boolean('signin'); $this->oauth_token = $this->arg('oauth_token'); $this->verifier = $this->arg('oauth_verifier'); if ($this->scoped instanceof Profile) { - $flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); + try { + $flink = Foreign_link::getByUserID($this->scoped->getID(), TWITTER_SERVICE); + $fuser = $flink->getForeignUser(); - // If there's already a foreign link record and a foreign user - // it means the accounts are already linked, and this is unecessary. - // So go back. + // If there's already a foreign link record and a foreign user + // (no exceptions were thrown when fetching either of them...) + // it means the accounts are already linked, and this is unecessary. + // So go back. - if ($flink instanceof Foreign_link) { - try { - $fuser = $flink->getForeignUser(); - common_redirect(common_local_url('twittersettings')); - } catch (NoResultException $e) { - // ok, so no foreign user but there's a foreign link?? - // this logic is left since the StatusNet days - } + common_redirect(common_local_url('twittersettings')); + } catch (NoResultException $e) { + // but if we don't have a foreign user linked, let's continue authorization procedure. } } } @@ -96,16 +92,18 @@ class TwitterauthorizationAction extends FormAction $this->access_token = new OAuthToken($this->trimmed('access_token_key'), $this->trimmed('access_token_secret')); if ($this->arg('create')) { + common_debug('TwitterBridgeDebug - POST with create'); if (!$this->boolean('license')) { // TRANS: Form validation error displayed when the checkbox to agree to the license has not been checked. throw new ClientException(_m('You cannot register if you do not agree to the license.')); } return $this->createNewUser(); - } else if ($this->arg('connect')) { + } elseif ($this->arg('connect')) { + common_debug('TwitterBridgeDebug - POST with connect'); return $this->connectNewUser(); } - common_debug('Twitter bridge - ' . print_r($this->args, true)); + common_debug('TwitterBridgeDebug - ' . print_r($this->args, true)); // TRANS: Form validation error displayed when an unhandled error occurs. throw new ClientException(_m('No known action for POST.')); } @@ -125,7 +123,7 @@ class TwitterauthorizationAction extends FormAction $_SESSION['twitter_request_token'] = $req_tok->key; $_SESSION['twitter_request_token_secret'] = $req_tok->secret; - $auth_link = $client->getAuthorizeLink($req_tok, $this->signin); + $auth_link = $client->getTwitterAuthorizeLink($req_tok, $this->boolean('signin')); } catch (OAuthClientException $e) { $msg = sprintf( 'OAuth client error - code: %1s, msg: %2s', @@ -270,11 +268,13 @@ class TwitterauthorizationAction extends FormAction // $this->oauth_token is only populated once Twitter authorizes our // request token. If it's empty we're at the beginning of the auth // process - if (empty($this->oauth_token)) { - // authorizeRequestToken either throws an exception or redirects - $this->authorizeRequestToken(); - } else { - $this->saveAccessToken(); + if (empty($this->error)) { + if (empty($this->oauth_token)) { + // authorizeRequestToken either throws an exception or redirects + $this->authorizeRequestToken(); + } else { + $this->saveAccessToken(); + } } parent::showPage(); @@ -302,8 +302,8 @@ class TwitterauthorizationAction extends FormAction $this->hidden('tw_fields_name', $this->tw_fields['fullname']); $this->hidden('token', common_session_token()); - // Don't allow new account creation if site is flagged as invite only - if (common_config('site', 'inviteonly') == false) { + // Only allow new account creation if site is not flagged invite-only + if (!common_config('site', 'inviteonly')) { $this->elementStart('fieldset'); $this->element('legend', null, // TRANS: Fieldset legend. @@ -418,14 +418,17 @@ class TwitterauthorizationAction extends FormAction return ''; } - function createNewUser() + protected function createNewUser() { + common_debug('TwitterBridgeDebug - createNewUser'); if (!Event::handle('StartRegistrationTry', array($this))) { + common_debug('TwitterBridgeDebug - StartRegistrationTry failed'); // TRANS: Client error displayed when trying to create a new user but a plugin aborted the process. throw new ClientException(_m('Registration of new user was aborted, maybe you failed a captcha?')); } if (common_config('site', 'closed')) { + common_debug('TwitterBridgeDebug - site is closed for registrations'); // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed. throw new ClientException(_m('Registration not allowed.')); } @@ -433,6 +436,7 @@ class TwitterauthorizationAction extends FormAction $invite = null; if (common_config('site', 'inviteonly')) { + common_debug('TwitterBridgeDebug - site is inviteonly'); $code = $_SESSION['invitecode']; if (empty($code)) { // TRANS: Client error displayed when trying to create a new user while creating new users is not allowed. @@ -442,11 +446,13 @@ class TwitterauthorizationAction extends FormAction $invite = Invitation::getKV('code', $code); if (!$invite instanceof Invite) { + common_debug('TwitterBridgeDebug - and we failed the invite code test'); // TRANS: Client error displayed when trying to create a new user with an invalid invitation code. throw new ClientException(_m('Not a valid invitation code.')); } } + common_debug('TwitterBridgeDebug - trying our nickname: '.$this->trimmed('newname')); // Nickname::normalize throws exception if the nickname is taken $nickname = Nickname::normalize($this->trimmed('newname'), true); @@ -463,12 +469,16 @@ class TwitterauthorizationAction extends FormAction $args['email'] = $email; } + common_debug('TwitterBridgeDebug - registering user with args:'.var_export($args,true)); $user = User::register($args); + common_debug('TwitterBridgeDebug - registered the user and saving foreign link for '.$user->id); + $this->saveForeignLink($user->id, $this->twuid, $this->access_token); + common_debug('TwitterBridgeDebug - saving twitter user after creating new local user '.$user->id); save_twitter_user($this->twuid, $this->tw_fields['screen_name']); common_set_user($user); @@ -534,19 +544,16 @@ class TwitterauthorizationAction extends FormAction common_redirect(common_local_url('twittersettings'), 303); } - function tryLogin() + protected function tryLogin() { common_debug('TwitterBridge Plugin - ' . "Trying login for Twitter user $this->twuid."); - $flink = Foreign_link::getByForeignID($this->twuid, - TWITTER_SERVICE); - - if (!empty($flink)) { + try { + $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); $user = $flink->getUser(); - if (!empty($user)) { - + if ($user instanceof User) { common_debug('TwitterBridge Plugin - ' . "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); @@ -554,9 +561,11 @@ class TwitterauthorizationAction extends FormAction common_real_login(true); $this->goHome($user->nickname); } + } catch (NoResultException $e) { + // Either no Foreign_link was found or not the user connected to it. + // Let's just continue to allow creating or logging in as a new user. } - common_debug('TwitterBridge Plugin - ' . - "No flink found for twuid: $this->twuid - new user"); + common_debug("TwitterBridge Plugin - No flink found for twuid: {$this->twuid} - new user"); return; throw new ServerException(_m('No foreign link found for Twitter user')); diff --git a/plugins/TwitterBridge/actions/twittersettings.php b/plugins/TwitterBridge/actions/twittersettings.php index efde36797c..6665ae619e 100644 --- a/plugins/TwitterBridge/actions/twittersettings.php +++ b/plugins/TwitterBridge/actions/twittersettings.php @@ -216,25 +216,15 @@ class TwittersettingsAction extends ProfileSettingsAction * * @return void */ - function handlePost() + protected function doPost() { - // CSRF protection - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - // TRANS: Client error displayed when the session token does not match or is not given. - $this->showForm(_m('There was a problem with your session token. '. - 'Try again, please.')); - return; - } - if ($this->arg('save')) { - $this->savePreferences(); + return $this->savePreferences(); } else if ($this->arg('disconnect')) { - $this->removeTwitterAccount(); - } else { - // TRANS: Client error displayed when the submitted form contains unexpected data. - $this->showForm(_m('Unexpected form submission.')); + return $this->removeTwitterAccount(); } + // TRANS: Client error displayed when the submitted form contains unexpected data. + throw new ClientException(_m('Unexpected form submission.')); } /** @@ -242,26 +232,26 @@ class TwittersettingsAction extends ProfileSettingsAction * * @return void */ - function removeTwitterAccount() + protected function removeTwitterAccount() { - $user = common_current_user(); - $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); - - if (empty($flink)) { - // TRANS: Client error displayed when trying to remove a connected Twitter account when there isn't one connected. - $this->clientError(_m('No Twitter connection to remove.')); + if (!$this->flink instanceof Foreign_link) { + // TRANS: Error message possibly displayed when trying to remove a connected Twitter account when there isn't one connected. + throw new AlreadyFulfilledException(_m('No Twitter connection to remove.')); } - $result = $flink->safeDelete(); + $result = $this->flink->safeDelete(); - if (empty($result)) { - common_log_db_error($flink, 'DELETE', __FILE__); + if ($result === false) { + common_log_db_error($this->flink, 'DELETE', __FILE__); // TRANS: Server error displayed when trying to remove a connected Twitter account fails. - $this->serverError(_m('Could not remove Twitter user.')); + throw new ServerException(_m('Could not remove Twitter user.')); } + $this->flink = null; + $this->fuser = null; + // TRANS: Success message displayed after disconnecting a Twitter account. - $this->showForm(_m('Twitter account disconnected.'), true); + return _m('Twitter account disconnected.'); } /** @@ -269,7 +259,7 @@ class TwittersettingsAction extends ProfileSettingsAction * * @return void */ - function savePreferences() + protected function savePreferences() { $noticesend = $this->boolean('noticesend'); $noticerecv = $this->boolean('noticerecv'); @@ -298,7 +288,7 @@ class TwittersettingsAction extends ProfileSettingsAction } // TRANS: Success message after saving Twitter integration preferences. - $this->showForm(_m('Twitter preferences saved.'), true); + return _m('Twitter preferences saved.'); } /** diff --git a/plugins/TwitterBridge/lib/twitteroauthclient.php b/plugins/TwitterBridge/lib/twitteroauthclient.php index 9d3b244724..93d10b8d48 100644 --- a/plugins/TwitterBridge/lib/twitteroauthclient.php +++ b/plugins/TwitterBridge/lib/twitteroauthclient.php @@ -126,7 +126,7 @@ class TwitterOAuthClient extends OAuthClient * * @return the link */ - function getAuthorizeLink($request_token, $signin = false) + function getTwitterAuthorizeLink($request_token, $signin = false) { $url = ($signin) ? self::$signinUrl : self::$authorizeURL; From 6f62adedfc44306e1df2a63e030f8b29e334f3a2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 02:16:52 +0200 Subject: [PATCH 085/156] Infinite loop on CLI initiated profile deletion for local users profile deleting user deleting profile deleting user... --- classes/Profile.php | 5 +++++ classes/User.php | 6 ++++-- lib/deluserqueuehandler.php | 9 +++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/classes/Profile.php b/classes/Profile.php index 5359ffb58b..09f9ca71d1 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -877,6 +877,11 @@ class Profile extends Managed_DataObject function delete($useWhere=false) { + // just in case it hadn't been done before... (usually set before adding deluser to queue handling!) + if (!$this->hasRole(Profile_role::DELETED)) { + $this->grantRole(Profile_role::DELETED); + } + $this->_deleteNotices(); $this->_deleteSubscriptions(); $this->_deleteTags(); diff --git a/classes/User.php b/classes/User.php index 5b9d7b51fe..e33c83e89c 100644 --- a/classes/User.php +++ b/classes/User.php @@ -598,8 +598,10 @@ class User extends Managed_DataObject } try { - $profile = $this->getProfile(); - $profile->delete(); + if (!$this->hasRole(Profile_role::DELETED)) { + $profile = $this->getProfile(); + $profile->delete(); + } } catch (UserNoProfileException $unp) { common_log(LOG_INFO, "User {$this->nickname} has no profile; continuing deletion."); } diff --git a/lib/deluserqueuehandler.php b/lib/deluserqueuehandler.php index 1baaf9331f..65866af418 100644 --- a/lib/deluserqueuehandler.php +++ b/lib/deluserqueuehandler.php @@ -74,8 +74,13 @@ class DelUserQueueHandler extends QueueHandler $qm = QueueManager::get(); $qm->enqueue($user, 'deluser'); } else { - // Out of notices? Let's finish deleting this guy! - $user->delete(); + // Out of notices? Let's finish deleting this profile! + try { + $user->getProfile()->delete(); + } catch (UserNoProfileException $e) { + // in case a profile didn't exist for some reason, just delete the User directly + $user->delete(); + } common_log(LOG_INFO, "User $user->id $user->nickname deleted."); return true; } From b609a3610fd3e61a11dc6b278bcc4eefee347897 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 11:04:35 +0200 Subject: [PATCH 086/156] Some missed exception throwing since fixing Foreign_link and Foreign_user --- plugins/TwitterBridge/TwitterBridgePlugin.php | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 84c6285029..dd3007e309 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -387,9 +387,9 @@ class TwitterBridgePlugin extends Plugin if ($n2s instanceof Notice_to_status) { - $flink = Foreign_link::getByUserID($notice->profile_id, TWITTER_SERVICE); // twitter service - - if (!$flink instanceof Foreign_link) { + try { + $flink = Foreign_link::getByUserID($notice->profile_id, TWITTER_SERVICE); // twitter service + } catch (NoResultException $e) { return true; } @@ -421,9 +421,9 @@ class TwitterBridgePlugin extends Plugin */ function onEndFavorNotice(Profile $profile, Notice $notice) { - $flink = Foreign_link::getByUserID($profile->getID(), TWITTER_SERVICE); // twitter service - - if (!$flink instanceof Foreign_link) { + try { + $flink = Foreign_link::getByUserID($profile->getID(), TWITTER_SERVICE); // twitter service + } catch (NoResultException $e) { return true; } @@ -460,10 +460,9 @@ class TwitterBridgePlugin extends Plugin */ function onEndDisfavorNotice(Profile $profile, Notice $notice) { - $flink = Foreign_link::getByUserID($profile->id, - TWITTER_SERVICE); // twitter service - - if (empty($flink)) { + try { + $flink = Foreign_link::getByUserID($profile->getID(), TWITTER_SERVICE); // twitter service + } catch (NoResultException $e) { return true; } @@ -520,7 +519,7 @@ class TwitterBridgePlugin extends Plugin "text" => sprintf(_("@%s on Twitter"), $fuser->nickname), "image" => $this->path("icons/twitter-bird-white-on-blue.png")); } catch (NoResultException $e) { - // no foreign link for Twitter on this profile ID + // no foreign link and/or user for Twitter on this profile ID } return true; @@ -564,16 +563,17 @@ class TwitterBridgePlugin extends Plugin if( count($noticeArray) != 1 ) { break; } $post = $noticeArray[0]; - $flink = Foreign_link::getByUserID($post->profile_id, TWITTER_SERVICE); - if( $flink ) { // Our local user has registered Twitter Gateway + try { + $flink = Foreign_link::getByUserID($post->profile_id, TWITTER_SERVICE); $fuser = Foreign_user::getForeignUser($flink->foreign_id, TWITTER_SERVICE); - if( $fuser ) { // Got nickname for local user's Twitter account - $action->element('meta', array('name' => 'twitter:creator', - 'content' => '@'.$fuser->nickname)); - } + $action->element('meta', array('name' => 'twitter:creator', + 'content' => '@'.$fuser->nickname)); + } catch (NoResultException $e) { + // no foreign link and/or user for Twitter on this profile ID } break; - default: break; + default: + break; } return true; From e0084a6fdf314365c6e58334bdd44a99f8e79bd1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 11:39:34 +0200 Subject: [PATCH 087/156] Exception handling regarding Foreign_link --- classes/Foreign_link.php | 4 +- .../actions/facebookfinishlogin.php | 40 +++++++-------- plugins/FacebookBridge/lib/facebookclient.php | 11 ++-- .../actions/twitterauthorization.php | 13 +++-- .../TwitterBridge/actions/twittersettings.php | 2 +- .../daemons/synctwitterfriends.php | 50 +++++++++---------- .../daemons/twitterstatusfetcher.php | 9 +--- .../TwitterBridge/lib/tweetinqueuehandler.php | 6 +-- plugins/TwitterBridge/lib/twitterimport.php | 22 ++++---- plugins/TwitterBridge/scripts/fakestream.php | 7 +-- plugins/TwitterBridge/scripts/streamtest.php | 7 +-- plugins/TwitterBridge/twitter.php | 19 +++++-- 12 files changed, 88 insertions(+), 102 deletions(-) diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 6e050d0c94..b3757448ad 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -136,12 +136,12 @@ class Foreign_link extends Managed_DataObject function getUser() { - return User::getKV($this->user_id); + return Profile::getByID($this->user_id)->getUser(); } function getProfile() { - return Profile::getKV('id', $this->user_id); + return Profile::getByID($this->user_id); } // Make sure we only ever delete one record at a time diff --git a/plugins/FacebookBridge/actions/facebookfinishlogin.php b/plugins/FacebookBridge/actions/facebookfinishlogin.php index 080c59612c..7cf493a994 100644 --- a/plugins/FacebookBridge/actions/facebookfinishlogin.php +++ b/plugins/FacebookBridge/actions/facebookfinishlogin.php @@ -519,34 +519,30 @@ class FacebookfinishloginAction extends Action function tryLogin() { - $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); - - if (!empty($flink)) { + try { + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); $user = $flink->getUser(); - if (!empty($user)) { + common_log( + LOG_INFO, + sprintf( + 'Logged in Facebook user %s as user %d (%s)', + $this->fbuid, + $user->nickname, + $user->id + ), + __FILE__ + ); - common_log( - LOG_INFO, - sprintf( - 'Logged in Facebook user %s as user %d (%s)', - $this->fbuid, - $user->nickname, - $user->id - ), - __FILE__ - ); + common_set_user($user); + common_real_login(true); - common_set_user($user); - common_real_login(true); + // clear out the stupid cookie + setcookie('fb_access_token', '', time() - 3600); // one hour ago - // clear out the stupid cookie - setcookie('fb_access_token', '', time() - 3600); // one hour ago + $this->goHome($user->nickname); - $this->goHome($user->nickname); - } - - } else { + } catch (NoResultException $e) { $this->showForm(null, $this->bestNewNickname()); } } diff --git a/plugins/FacebookBridge/lib/facebookclient.php b/plugins/FacebookBridge/lib/facebookclient.php index e55e19d88b..5faad5dfb7 100644 --- a/plugins/FacebookBridge/lib/facebookclient.php +++ b/plugins/FacebookBridge/lib/facebookclient.php @@ -66,13 +66,12 @@ class Facebookclient $this->notice = $notice; $profile_id = $profile ? $profile->id : $notice->profile_id; - $this->flink = Foreign_link::getByUserID( - $profile_id, - FACEBOOK_SERVICE - ); - - if (!empty($this->flink)) { + try { + $this->flink = Foreign_link::getByUserID($profile_id, FACEBOOK_SERVICE); $this->user = $this->flink->getUser(); + } catch (NoResultException $e) { + // at least $this->flink could've gotten set to something, + // but the logic that was here before didn't care, so let's not care either } } diff --git a/plugins/TwitterBridge/actions/twitterauthorization.php b/plugins/TwitterBridge/actions/twitterauthorization.php index 3eb302bda4..c9b892b640 100644 --- a/plugins/TwitterBridge/actions/twitterauthorization.php +++ b/plugins/TwitterBridge/actions/twitterauthorization.php @@ -553,20 +553,19 @@ class TwitterauthorizationAction extends FormAction $flink = Foreign_link::getByForeignID($this->twuid, TWITTER_SERVICE); $user = $flink->getUser(); - if ($user instanceof User) { - common_debug('TwitterBridge Plugin - ' . - "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); + common_debug('TwitterBridge Plugin - ' . + "Logged in Twitter user $flink->foreign_id as user $user->id ($user->nickname)"); - common_set_user($user); - common_real_login(true); - $this->goHome($user->nickname); - } + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); } catch (NoResultException $e) { // Either no Foreign_link was found or not the user connected to it. // Let's just continue to allow creating or logging in as a new user. } common_debug("TwitterBridge Plugin - No flink found for twuid: {$this->twuid} - new user"); + // FIXME: what do we want to do here? I forgot return; throw new ServerException(_m('No foreign link found for Twitter user')); } diff --git a/plugins/TwitterBridge/actions/twittersettings.php b/plugins/TwitterBridge/actions/twittersettings.php index 6665ae619e..ccdb44fcb9 100644 --- a/plugins/TwitterBridge/actions/twittersettings.php +++ b/plugins/TwitterBridge/actions/twittersettings.php @@ -184,7 +184,7 @@ class TwittersettingsAction extends ProfileSettingsAction $this->elementEnd('ul'); - if ($this->flink) { + if ($this->flink instanceof Foreign_link) { // TRANS: Button text for saving Twitter integration settings. $this->submit('save', _m('BUTTON','Save')); } else { diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index a3862eedfd..9fa3b282b4 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -104,6 +104,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon return $flinks; } + // FIXME: make it so we can force a Foreign_link here without colliding with parent function childTask($flink) { // Each child ps needs its own DB connection @@ -124,7 +125,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon unset($_DB_DATAOBJECT['CONNECTIONS']); } - function fetchTwitterFriends($flink) + function fetchTwitterFriends(Foreign_link $flink) { $friends = array(); @@ -192,8 +193,14 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon return $friends; } - function subscribeTwitterFriends($flink) + function subscribeTwitterFriends(Foreign_link $flink) { + try { + $profile = $flink->getProfile(); + } catch (NoResultException $e) { + common_log(LOG_WARNING, 'Foreign_link has no matching local profile for local ID: '.$flink->user_id); + } + $friends = $this->fetchTwitterFriends($flink); if (empty($friends)) { @@ -203,8 +210,6 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon return false; } - $profile = $flink->getProfile(); - foreach ($friends as $friend) { $friend_name = $friend->screen_name; @@ -219,31 +224,24 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon continue; } - // Check to see if there's a related local user - - $friend_flink = Foreign_link::getByForeignID($friend_id, - TWITTER_SERVICE); - - if (!empty($friend_flink)) { + // Check to see if there's a related local user and try to subscribe + try { + $friend_flink = Foreign_link::getByForeignID($friend_id, TWITTER_SERVICE); // Get associated user and subscribe her + $friend_profile = $friend_flink->getProfile(); - $friend_profile = Profile::getKV('id', $friend_flink->user_id); - - if ($friend_profile instanceof Profile) { - try { - $other = Profile::getKV('id', $invites->user_id); - Subscription::start($profile, $friend_profile); - common_log(LOG_INFO, - $this->name() . ' - Subscribed ' . - "{$friend_profile->nickname} to {$profile->nickname}."); - } catch (Exception $e) { - common_debug($this->name() . - ' - Tried and failed subscribing ' . - "{$friend_profile->nickname} to {$profile->nickname} - " . - $e->getMessage()); - } - } + Subscription::start($profile, $friend_profile); + common_log(LOG_INFO, + $this->name() . ' - Subscribed ' . + "{$friend_profile->nickname} to {$profile->nickname}."); + } catch (NoResultException $e) { + // either no foreign link for this friend's foreign ID or no profile found on local ID. + } catch (Exception $e) { + common_debug($this->name() . + ' - Tried and failed subscribing ' . + "{$friend_profile->nickname} to {$profile->nickname} - " . + $e->getMessage()); } } diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index d444b8aa5d..83e8a0df5e 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -128,6 +128,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon return $flinks; } + // FIXME: make it so we can force a Foreign_link here without colliding with parent function childTask($flink) { // Each child ps needs its own DB connection @@ -149,14 +150,8 @@ class TwitterStatusFetcher extends ParallelizingDaemon unset($_DB_DATAOBJECT['CONNECTIONS']); } - function getTimeline($flink, $timelineUri = 'home_timeline') + function getTimeline(Foreign_link $flink, $timelineUri = 'home_timeline') { - if (empty($flink)) { - common_log(LOG_ERR, $this->name() . - " - Can't retrieve Foreign_link for foreign ID $fid"); - return; - } - common_log(LOG_DEBUG, $this->name() . ' - Trying to get ' . $timelineUri . ' timeline for Twitter user ' . $flink->foreign_id); diff --git a/plugins/TwitterBridge/lib/tweetinqueuehandler.php b/plugins/TwitterBridge/lib/tweetinqueuehandler.php index cc0c05f9a6..69ce5a61e9 100644 --- a/plugins/TwitterBridge/lib/tweetinqueuehandler.php +++ b/plugins/TwitterBridge/lib/tweetinqueuehandler.php @@ -51,8 +51,8 @@ class TweetInQueueHandler extends QueueHandler $importer = new TwitterImport(); $notice = $importer->importStatus($status); if ($notice instanceof Notice) { - $flink = Foreign_link::getByForeignID($receiver, TWITTER_SERVICE); - if ($flink instanceof Foreign_link) { + try { + $flink = Foreign_link::getByForeignID($receiver, TWITTER_SERVICE); common_log(LOG_DEBUG, "TweetInQueueHandler - Got flink so add notice ". $notice->id." to attentions for user ".$flink->user_id); try { @@ -63,7 +63,7 @@ class TweetInQueueHandler extends QueueHandler common_log(LOG_ERR, "Failed adding notice {$notice->id} to attentions for user {$flink->user_id}: " . $e->getMessage()); } - } else { + } catch (NoResultException $e) { common_log(LOG_DEBUG, "TweetInQueueHandler - No flink found for foreign user ".$receiver); } } diff --git a/plugins/TwitterBridge/lib/twitterimport.php b/plugins/TwitterBridge/lib/twitterimport.php index 45b7547ce2..d929fecf83 100644 --- a/plugins/TwitterBridge/lib/twitterimport.php +++ b/plugins/TwitterBridge/lib/twitterimport.php @@ -542,17 +542,17 @@ class TwitterImport } foreach ($status->entities->user_mentions as $mention) { - $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE); - if (!empty($flink)) { - $user = User::getKV('id', $flink->user_id); - if (!empty($user)) { - $reply = new Reply(); - $reply->notice_id = $notice->id; - $reply->profile_id = $user->id; - $reply->modified = $notice->created; - common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}"); - $id = $reply->insert(); - } + try { + $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE); + $user = $flink->getUser(); + $reply = new Reply(); + $reply->notice_id = $notice->id; + $reply->profile_id = $user->id; + $reply->modified = $notice->created; + common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}"); + $id = $reply->insert(); + } catch (NoResultException $e) { + common_log(LOG_WARNING, 'No local user found for Foreign_link with local User id: '.$flink->user_id); } } } diff --git a/plugins/TwitterBridge/scripts/fakestream.php b/plugins/TwitterBridge/scripts/fakestream.php index e827a07117..5d965e7394 100644 --- a/plugins/TwitterBridge/scripts/fakestream.php +++ b/plugins/TwitterBridge/scripts/fakestream.php @@ -62,12 +62,7 @@ if (have_option('n')) { */ function twitterAuthForUser(User $user) { - $flink = Foreign_link::getByUserID($user->id, - TWITTER_SERVICE); - if (!$flink) { - throw new ServerException("No Twitter config for this user."); - } - + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); $token = TwitterOAuthClient::unpackToken($flink->credentials); if (!$token) { throw new ServerException("No Twitter OAuth credentials for this user."); diff --git a/plugins/TwitterBridge/scripts/streamtest.php b/plugins/TwitterBridge/scripts/streamtest.php index 4e8340bb3f..a642920cee 100644 --- a/plugins/TwitterBridge/scripts/streamtest.php +++ b/plugins/TwitterBridge/scripts/streamtest.php @@ -63,12 +63,7 @@ if (have_option('n')) { */ function twitterAuthForUser(User $user) { - $flink = Foreign_link::getByUserID($user->id, - TWITTER_SERVICE); - if (!$flink) { - throw new ServerException("No Twitter config for this user."); - } - + $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE); $token = TwitterOAuthClient::unpackToken($flink->credentials); if (!$token) { throw new ServerException("No Twitter OAuth credentials for this user."); diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index bdcd61db45..0f1e686ac8 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -181,11 +181,15 @@ function twitter_id($status, $field='id') */ function broadcast_twitter($notice) { - $flink = Foreign_link::getByUserID($notice->profile_id, - TWITTER_SERVICE); + try { + $flink = Foreign_link::getByUserID($notice->profile_id, TWITTER_SERVICE); + } catch (NoResultException $e) { + // Alright so don't broadcast it then! (since there's no foreign link) + return true; + } // Don't bother with basic auth, since it's no longer allowed - if (!empty($flink) && TwitterOAuthClient::isPackedToken($flink->credentials)) { + if (TwitterOAuthClient::isPackedToken($flink->credentials)) { if (is_twitter_bound($notice, $flink)) { if (!empty($notice->repeat_of) && is_twitter_notice($notice->repeat_of)) { $retweet = retweet_notice($flink, Notice::getKV('id', $notice->repeat_of)); @@ -273,8 +277,13 @@ function twitter_update_params($notice) return $params; } -function broadcast_oauth($notice, $flink) { - $user = $flink->getUser(); +function broadcast_oauth($notice, Foreign_link $flink) { + try { + $user = $flink->getUser(); + } catch (ServerException $e) { + common_log(LOG_WARNING, 'Discarding broadcast_oauth for notice '.$notice->id.' because of exception: '.$e->getMessage()); + return true; + } $statustxt = format_status($notice); $params = twitter_update_params($notice); From 5b09a150bc7face85a63e0deca019fb093ed4f48 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 18 Jul 2015 19:19:16 +0200 Subject: [PATCH 088/156] Increased debugging and fixing conversation stitching for saveActivity --- classes/Notice.php | 38 ++++++++++++++++++++++++++------------ lib/threadednoticelist.php | 2 +- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 6301f9ce62..050eeaf90d 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -523,18 +523,13 @@ class Notice extends Managed_DataObject // Handle repeat case - if (isset($repeat_of)) { + if (!empty($options['repeat_of'])) { // Check for a private one - $repeat = Notice::getKV('id', $repeat_of); + $repeat = Notice::getByID($options['repeat_of']); - if (!($repeat instanceof Notice)) { - // TRANS: Client exception thrown in notice when trying to repeat a missing or deleted notice. - throw new ClientException(_('Cannot repeat; original notice is missing or deleted.')); - } - - if ($profile->id == $repeat->profile_id) { + if ($profile->sameAs($repeat->getProfile())) { // TRANS: Client error displayed when trying to repeat an own notice. throw new ClientException(_('You cannot repeat your own notice.')); } @@ -610,12 +605,13 @@ class Notice extends Managed_DataObject if (empty($notice->conversation) and !empty($options['conversation'])) { $conv = Conversation::getKV('uri', $options['conversation']); if ($conv instanceof Conversation) { - common_debug('Conversation stitched together from (probably) reply to unknown remote user. Activity creation time ('.$notice->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); + common_debug('Conversation stitched together from (probably) a reply to unknown remote user. Activity creation time ('.$notice->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); $notice->conversation = $conv->id; } else { // Conversation URI was not found, so we must create it. But we can't create it // until we have a Notice ID because of the database layout... - $notice->tmp_conv_uri = $options['conversation']; + // $options['conversation'] will be used later after the $notice->insert(); + common_debug('Conversation URI not found, so we have to create it after inserting this Notice: '.$options['conversation']); } } else { // If we're not using the attached conversation URI let's remove it @@ -677,6 +673,7 @@ class Notice extends Managed_DataObject if (empty($notice->conversation)) { $orig = clone($notice); // $act->context->conversation will be null if it was not provided + $conv = Conversation::create($notice, $options['conversation']); $notice->conversation = $conv->id; $notice->update($orig); @@ -853,6 +850,22 @@ class Notice extends Managed_DataObject if (is_null($scope)) { $scope = $reply->scope; } + } else { + // If we don't know the reply, we might know the conversation! + // This will happen if a known remote user replies to an + // unknown remote user - within a known conversation. + if (empty($stored->conversation) and !empty($act->context->conversation)) { + $conv = Conversation::getKV('uri', $act->context->conversation); + if ($conv instanceof Conversation) { + common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time ('.$stored->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); + $stored->conversation = $conv->id; + } else { + // Conversation URI was not found, so we must create it. But we can't create it + // until we have a Notice ID because of the database layout... + // $options['conversation'] will be used later after the $stored->insert(); + common_debug('Conversation URI from activity context not found, so we have to create it after inserting this Notice: '.$act->context->conversation); + } + } } if ($act->context instanceof ActivityContext) { @@ -898,10 +911,11 @@ class Notice extends Managed_DataObject 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 it's not part of a conversation, it's the beginning + // of a new one (or belongs to a previously unknown URI). if (empty($stored->conversation)) { // $act->context->conversation will be null if it was not provided + common_debug('Creating a new conversation for stored notice ID='.$stored->getID().' with URI: '.$act->context->conversation); $conv = Conversation::create($stored, $act->context->conversation); $stored->conversation = $conv->id; } diff --git a/lib/threadednoticelist.php b/lib/threadednoticelist.php index acb9efb972..ebf0a46089 100644 --- a/lib/threadednoticelist.php +++ b/lib/threadednoticelist.php @@ -195,7 +195,7 @@ class ThreadedNoticeListItem extends NoticeListItem function showEnd() { $max = $this->initialItems(); - if (!$this->repeat) { + if (!$this->repeat instanceof Notice) { $stream = new ConversationNoticeStream($this->notice->conversation, $this->userProfile); $notice = $stream->getNotices(0, $max + 2); $notices = array(); From 7ce32619cc793b5b18ec5814f5e0551dcb1b5490 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 21 Jul 2015 02:17:34 +0200 Subject: [PATCH 089/156] Missing getTarget function in targetedrss10action.php --- lib/targetedrss10action.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/targetedrss10action.php b/lib/targetedrss10action.php index c7615bd36a..ed7e262623 100644 --- a/lib/targetedrss10action.php +++ b/lib/targetedrss10action.php @@ -39,6 +39,11 @@ class TargetedRss10Action extends Rss10Action $this->target = User::getByNickname($this->trimmed('nickname'))->getProfile(); } + public function getTarget() + { + return $this->target; + } + function getImage() { return $this->target->avatarUrl(AVATAR_PROFILE_SIZE); From 266b032b170b0f4ad3d6d2f547a31e9e5ce33000 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 21 Jul 2015 02:32:17 +0200 Subject: [PATCH 090/156] UsergroupbyidAction now extends ManagedAction --- actions/groupbyid.php | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/actions/groupbyid.php b/actions/groupbyid.php index 8556675155..de87ec5c67 100644 --- a/actions/groupbyid.php +++ b/actions/groupbyid.php @@ -42,53 +42,22 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class GroupbyidAction extends Action +class GroupbyidAction extends ManagedAction { /** group we're viewing. */ - var $group = null; + protected $group = null; - /** - * Is this page read-only? - * - * @return boolean true - */ function isReadOnly($args) { return true; } - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - - $id = $this->arg('id'); - - if (!$id) { - // TRANS: Client error displayed referring to a group's permalink without providing a group ID. - $this->clientError(_('No ID.')); - } - - common_debug("Got ID $id"); - - $this->group = User_group::getKV('id', $id); - - if (!$this->group) { - // TRANS: Client error displayed referring to a group's permalink for a non-existing group ID. - $this->clientError(_('No such group.'), 404); - } - - return true; + $this->group = User_group::getByID($this->arg('id')); } - /** - * Handle the request - * - * Shows a profile for the group, some controls, and a list of - * group notices. - * - * @return void - */ - function handle($args) + public function showPage() { common_redirect($this->group->homeUrl(), 303); } From 05b814ce68d3c879e252bfeee483f3c67ae07952 Mon Sep 17 00:00:00 2001 From: Chimo Date: Sat, 25 Jul 2015 09:37:10 -0400 Subject: [PATCH 091/156] Add nginx sample configuration --- INSTALL | 4 +++- nginx.conf.sample | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 nginx.conf.sample diff --git a/INSTALL b/INSTALL index 90fa84923b..64f30139bf 100644 --- a/INSTALL +++ b/INSTALL @@ -196,7 +196,9 @@ your server (like lighttpd or nginx). 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 + * For nginx, inspect the nginx.conf.sample file and apply the appropriate + changes. + * For other webservers, we gladly accept contributions of server configuration examples. 2. Assuming your webserver is properly configured and have its settings diff --git a/nginx.conf.sample b/nginx.conf.sample new file mode 100644 index 0000000000..d05c676bc1 --- /dev/null +++ b/nginx.conf.sample @@ -0,0 +1,53 @@ +server { + # Ports + listen 80; + # Uncomment the following line + # to enable HTTPS + #listen 443 ssl; + + # Server name + # Change "example.org" to your domain name + server_name example.org; + + # SSL + # Uncomment and change the paths to setup + # your SSL key/cert. See https://cipherli.st/ + # for more information + #ssl_certificate /path/to/ssl.cert; + #ssl_certificate_key /path/to/ssl.key; + + # Logs + # Uncomment and change the paths to setup + # logging + #access_log /path/to/access.log; + #error_log /path/to/error.log; + + # Root + # Change the path below to where you installed + # GNU social + root /path/to/gnusocial/root; + + # Index + index index.php; + + # PHP + location ~ \.php { + fastcgi_pass unix:/run/php-fpm/php-fpm.sock; + # Remove the "fastcgi_pass" line above and uncomment + # the one below to use TCP sockets instead of Unix sockets + #fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + include fastcgi.conf; + } + + # Location + location / { + try_files $uri $uri/ @gnusocial; + } + + # Fancy URLs + location @gnusocial { + rewrite ^(.*)$ /index.php?p=$1 last; + } +} + From abde7a268204ff2ff12db31a4a489709d84ca3a7 Mon Sep 17 00:00:00 2001 From: Chimo Date: Sat, 25 Jul 2015 10:34:59 -0400 Subject: [PATCH 092/156] Fix broken link to FAQ in web installer --- lib/installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/installer.php b/lib/installer.php index 0a46b2a508..29a4383302 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -622,7 +622,7 @@ abstract class Installer $this->updateStatus("GNU social has been installed at $link"); $this->updateStatus( - 'DONE! You can visit your new GNU social site (log in as "'.htmlspecialchars($this->adminNick).'"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the mailing list or IRC.. FAQ is found here.' + 'DONE! You can visit your new GNU social site (log in as "'.htmlspecialchars($this->adminNick).'"). If this is your first GNU social install, make your experience the best possible by visiting our resource site to join the mailing list or IRC. FAQ is found here.' ); return true; From 60e7dc1e39d4dab5bb46d3b364ab9aec74819323 Mon Sep 17 00:00:00 2001 From: chimo Date: Sat, 1 Aug 2015 09:24:05 -0400 Subject: [PATCH 093/156] Add a few missing 'attachments' config options show_thumbs show_html filename_base --- CONFIGURE | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONFIGURE b/CONFIGURE index 217b32cc7c..120db976df 100644 --- a/CONFIGURE +++ b/CONFIGURE @@ -563,6 +563,11 @@ sslserver: if specified, this server will be used when creating HTTPS sslpath: if this and the sslserver are specified, this path will be used when creating HTTPS URLs. Otherwise, the attachments|path value will be used. +show_thumbs: show thumbnails in notice lists for uploaded images, and photos + and videos linked remotely that provide oEmbed info. Defaults to true. +show_html: show (filtered) text/html attachments (and oEmbed HTML etc.). + Doesn't affect AJAX calls. Defaults to false. +filename_base: for new files, choose one: 'upload', 'hash'. Defaults to hash. group ----- From b4342434167f0ce0785b2de0efb58b94cf7cf9d2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 2 Aug 2015 13:39:38 +0200 Subject: [PATCH 094/156] OpenID extlib updated: Fixes CVE-2014-8150 --- extlib/Auth/OpenID/URINorm.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extlib/Auth/OpenID/URINorm.php b/extlib/Auth/OpenID/URINorm.php index c051b550aa..32e84588db 100644 --- a/extlib/Auth/OpenID/URINorm.php +++ b/extlib/Auth/OpenID/URINorm.php @@ -93,7 +93,17 @@ function Auth_OpenID_pct_encoded_replace_unreserved($mo) function Auth_OpenID_pct_encoded_replace($mo) { - return chr(intval($mo[1], 16)); + $code = intval($mo[1], 16); + + // Prevent request splitting by ignoring newline and space characters + if($code === 0xA || $code === 0xD || $code === ord(' ')) + { + return $mo[0]; + } + else + { + return chr($code); + } } function Auth_OpenID_remove_dot_segments($path) From c77bce12e53418c2457f17ce1e34238f7baa448d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 15 Aug 2015 11:48:39 +0200 Subject: [PATCH 095/156] Mf2 extlib update from https://github.com/indieweb/php-mf2/ --- extlib/Mf2/Parser.php | 380 +++++++++++++++++++++++++----------------- 1 file changed, 227 insertions(+), 153 deletions(-) diff --git a/extlib/Mf2/Parser.php b/extlib/Mf2/Parser.php index 27805f2324..b8a954f2c8 100644 --- a/extlib/Mf2/Parser.php +++ b/extlib/Mf2/Parser.php @@ -13,17 +13,17 @@ use stdClass; /** * Parse Microformats2 - * + * * Functional shortcut for the commonest cases of parsing microformats2 from HTML. - * + * * Example usage: - * + * * use Mf2; * $output = Mf2\parse('Barnaby Walters'); * echo json_encode($output, JSON_PRETTY_PRINT); - * + * * Produces: - * + * * { * "items": [ * { @@ -35,7 +35,7 @@ use stdClass; * ], * "rels": {} * } - * + * * @param string|DOMDocument $input The HTML string or DOMDocument object to parse * @param string $url The URL the input document was found at, for relative URL resolution * @param bool $convertClassic whether or not to convert classic microformats @@ -84,7 +84,7 @@ function fetch($url, $convertClassic = true, &$curlInfo=null) { /** * Unicode to HTML Entities * @param string $input String containing characters to convert into HTML entities - * @return string + * @return string */ function unicodeToHtmlEntities($input) { return mb_convert_encoding($input, 'HTML-ENTITIES', mb_detect_encoding($input)); @@ -92,10 +92,10 @@ function unicodeToHtmlEntities($input) { /** * Collapse Whitespace - * + * * Collapses any sequences of whitespace within a string into a single space * character. - * + * * @deprecated since v0.2.3 * @param string $str * @return string @@ -113,10 +113,10 @@ function unicodeTrim($str) { /** * Microformat Name From Class string - * - * Given the value of @class, get the relevant mf classnames (e.g. h-card, + * + * Given the value of @class, get the relevant mf classnames (e.g. h-card, * p-name). - * + * * @param string $class A space delimited list of classnames * @param string $prefix The prefix to look for * @return string|array The prefixed name of the first microfomats class found or false @@ -127,9 +127,9 @@ function mfNamesFromClass($class, $prefix='h-') { $matches = array(); foreach ($classes as $classname) { - $compare_classname = strtolower(' ' . $classname); - $compare_prefix = strtolower(' ' . $prefix); - if (stristr($compare_classname, $compare_prefix) !== false && ($compare_classname != $compare_prefix)) { + $compare_classname = ' ' . $classname; + $compare_prefix = ' ' . $prefix; + if (strstr($compare_classname, $compare_prefix) !== false && ($compare_classname != $compare_prefix)) { $matches[] = ($prefix === 'h-') ? $classname : substr($classname, strlen($prefix)); } } @@ -139,10 +139,10 @@ function mfNamesFromClass($class, $prefix='h-') { /** * Get Nested µf Property Name From Class - * - * Returns all the p-, u-, dt- or e- prefixed classnames it finds in a + * + * Returns all the p-, u-, dt- or e- prefixed classnames it finds in a * space-separated string. - * + * * @param string $class * @return array */ @@ -153,19 +153,24 @@ function nestedMfPropertyNamesFromClass($class) { $class = str_replace(array(' ', ' ', "\n"), ' ', $class); foreach (explode(' ', $class) as $classname) { foreach ($prefixes as $prefix) { - $compare_classname = strtolower(' ' . $classname); - if (stristr($compare_classname, $prefix) && ($compare_classname != $prefix)) { - $propertyNames = array_merge($propertyNames, mfNamesFromClass($classname, ltrim($prefix))); + // Check if $classname is a valid property classname for $prefix. + if (mb_substr($classname, 0, mb_strlen($prefix)) == $prefix && $classname != $prefix) { + $propertyName = mb_substr($classname, mb_strlen($prefix)); + $propertyNames[$propertyName][] = $prefix; } } } + + foreach ($propertyNames as $property => $prefixes) { + $propertyNames[$property] = array_unique($prefixes); + } return $propertyNames; } /** * Wraps mfNamesFromClass to handle an element as input (common) - * + * * @param DOMElement $e The element to get the classname for * @param string $prefix The prefix to look for * @return mixed See return value of mf2\Parser::mfNameFromClass() @@ -192,28 +197,27 @@ function convertTimeFormat($time) { $hh = $mm = $ss = ''; preg_match('/(\d{1,2}):?(\d{2})?:?(\d{2})?(a\.?m\.?|p\.?m\.?)?/i', $time, $matches); - // if no am/pm specified + // If no am/pm is specified: if (empty($matches[4])) { return $time; - } - // else am/pm specified - else { + } else { + // Otherwise, am/pm is specified. $meridiem = strtolower(str_replace('.', '', $matches[4])); - // hours + // Hours. $hh = $matches[1]; - // add 12 to the pm hours + // Add 12 to hours if pm applies. if ($meridiem == 'pm' && ($hh < 12)) { $hh += 12; } $hh = str_pad($hh, 2, '0', STR_PAD_LEFT); - // minutes + // Minutes. $mm = (empty($matches[2]) ) ? '00' : $matches[2]; - // seconds, only if supplied + // Seconds, only if supplied. if (!empty($matches[3])) { $ss = $matches[3]; } @@ -229,11 +233,11 @@ function convertTimeFormat($time) { /** * Microformats2 Parser - * + * * A class which holds state for parsing microformats2 from HTML. - * + * * Example usage: - * + * * use Mf2; * $parser = new Mf2\Parser('

Barnaby Walters

'); * $output = $parser->parse(); @@ -244,18 +248,18 @@ class Parser { /** @var DOMXPath object which can be used to query over any fragment*/ public $xpath; - + /** @var DOMDocument */ public $doc; - + /** @var SplObjectStorage */ protected $parsed; - + public $jsonMode; /** * Constructor - * + * * @param DOMDocument|string $input The data to parse. A string of HTML or a DOMDocument * @param string $url The URL of the parsed document, for relative URL resolution * @param boolean $jsonMode Whether or not to use a stdClass instance for an empty `rels` dictionary. This breaks PHP looping over rels, but allows the output to be correctly serialized as JSON. @@ -271,20 +275,20 @@ class Parser { $doc = new DOMDocument(); @$doc->loadHTML(''); } - + $this->xpath = new DOMXPath($doc); - + $baseurl = $url; foreach ($this->xpath->query('//base[@href]') as $base) { $baseElementUrl = $base->getAttribute('href'); - + if (parse_url($baseElementUrl, PHP_URL_SCHEME) === null) { /* The base element URL is relative to the document URL. * * :/ * * Perhaps the author was high? */ - + $baseurl = resolveUrl($url, $baseElementUrl); } else { $baseurl = $baseElementUrl; @@ -296,31 +300,31 @@ class Parser { foreach ($this->xpath->query('//template') as $templateEl) { $templateEl->parentNode->removeChild($templateEl); } - + $this->baseurl = $baseurl; $this->doc = $doc; $this->parsed = new SplObjectStorage(); $this->jsonMode = $jsonMode; } - + private function elementPrefixParsed(\DOMElement $e, $prefix) { if (!$this->parsed->contains($e)) $this->parsed->attach($e, array()); - + $prefixes = $this->parsed[$e]; $prefixes[] = $prefix; $this->parsed[$e] = $prefixes; } - + private function isElementParsed(\DOMElement $e, $prefix) { if (!$this->parsed->contains($e)) return false; - + $prefixes = $this->parsed[$e]; - + if (!in_array($prefix, $prefixes)) return false; - + return true; } @@ -352,72 +356,72 @@ class Parser { // TODO: figure out if this has problems with sms: and geo: URLs public function resolveUrl($url) { - // If the URL is seriously malformed it’s probably beyond the scope of this + // If the URL is seriously malformed it’s probably beyond the scope of this // parser to try to do anything with it. if (parse_url($url) === false) return $url; - + $scheme = parse_url($url, PHP_URL_SCHEME); - + if (empty($scheme) and !empty($this->baseurl)) { return resolveUrl($this->baseurl, $url); } else { return $url; } } - + // Parsing Functions - + /** - * Parse value-class/value-title on an element, joining with $separator if + * Parse value-class/value-title on an element, joining with $separator if * there are multiple. - * + * * @param \DOMElement $e * @param string $separator = '' if multiple value-title elements, join with this string * @return string|null the parsed value or null if value-class or -title aren’t in use */ public function parseValueClassTitle(\DOMElement $e, $separator = '') { $valueClassElements = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value ")]', $e); - + if ($valueClassElements->length !== 0) { // Process value-class stuff $val = ''; foreach ($valueClassElements as $el) { $val .= $this->textContent($el); } - + return unicodeTrim($val); } - + $valueTitleElements = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value-title ")]', $e); - + if ($valueTitleElements->length !== 0) { // Process value-title stuff $val = ''; foreach ($valueTitleElements as $el) { $val .= $el->getAttribute('title'); } - + return unicodeTrim($val); } - + // No value-title or -class in this element return null; } - + /** * Given an element with class="p-*", get it’s value - * + * * @param DOMElement $p The element to parse * @return string The plaintext value of $p, dependant on type * @todo Make this adhere to value-class */ public function parseP(\DOMElement $p) { $classTitle = $this->parseValueClassTitle($p, ' '); - + if ($classTitle !== null) return $classTitle; - + if ($p->tagName == 'img' and $p->getAttribute('alt') !== '') { $pValue = $p->getAttribute('alt'); } elseif ($p->tagName == 'area' and $p->getAttribute('alt') !== '') { @@ -429,13 +433,13 @@ class Parser { } else { $pValue = unicodeTrim($this->textContent($p)); } - + return $pValue; } /** * Given an element with class="u-*", get the value of the URL - * + * * @param DOMElement $u The element to parse * @return string The plaintext value of $u, dependant on type * @todo make this adhere to value-class @@ -443,18 +447,18 @@ class Parser { public function parseU(\DOMElement $u) { if (($u->tagName == 'a' or $u->tagName == 'area') and $u->getAttribute('href') !== null) { $uValue = $u->getAttribute('href'); - } elseif ($u->tagName == 'img' and $u->getAttribute('src') !== null) { + } elseif (in_array($u->tagName, array('img', 'audio', 'video', 'source')) and $u->getAttribute('src') !== null) { $uValue = $u->getAttribute('src'); } elseif ($u->tagName == 'object' and $u->getAttribute('data') !== null) { $uValue = $u->getAttribute('data'); } - + if (isset($uValue)) { return $this->resolveUrl($uValue); } - + $classTitle = $this->parseValueClassTitle($u); - + if ($classTitle !== null) { return $classTitle; } elseif ($u->tagName == 'abbr' and $u->getAttribute('title') !== null) { @@ -468,7 +472,7 @@ class Parser { /** * Given an element with class="dt-*", get the value of the datetime as a php date object - * + * * @param DOMElement $dt The element to parse * @param array $dates Array of dates processed so far * @return string The datetime string found @@ -477,11 +481,11 @@ class Parser { // Check for value-class pattern $valueClassChildren = $this->xpath->query('./*[contains(concat(" ", @class, " "), " value ") or contains(concat(" ", @class, " "), " value-title ")]', $dt); $dtValue = false; - + if ($valueClassChildren->length > 0) { // They’re using value-class $dateParts = array(); - + foreach ($valueClassChildren as $e) { if (strstr(' ' . $e->getAttribute('class') . ' ', ' value-title ')) { $title = $e->getAttribute('title'); @@ -591,16 +595,16 @@ class Parser { $dtValue = $dt->nodeValue; } - if ( preg_match('/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches) ) { + if (preg_match('/(\d{4}-\d{2}-\d{2})/', $dtValue, $matches)) { $dates[] = $matches[0]; } } /** - * if $dtValue is only a time and there are recently parsed dates, - * form the full date-time using the most recnetly parsed dt- value + * if $dtValue is only a time and there are recently parsed dates, + * form the full date-time using the most recently parsed dt- value */ - if ( (preg_match('/^\d{1,2}:\d{1,2}(Z?[+|-]\d{2}:?\d{2})?/', $dtValue) or preg_match('/^\d{1,2}[a|p]m/', $dtValue)) && !empty($dates) ) { + if ((preg_match('/^\d{1,2}:\d{1,2}(Z?[+|-]\d{2}:?\d{2})?/', $dtValue) or preg_match('/^\d{1,2}[a|p]m/', $dtValue)) && !empty($dates)) { $dtValue = convertTimeFormat($dtValue); $dtValue = end($dates) . 'T' . unicodeTrim($dtValue, 'T'); } @@ -613,15 +617,15 @@ class Parser { * * @param DOMElement $e The element to parse * @return string $e’s innerHTML - * + * * @todo need to mark this element as e- parsed so it doesn’t get parsed as it’s parent’s e-* too */ public function parseE(\DOMElement $e) { $classTitle = $this->parseValueClassTitle($e); - + if ($classTitle !== null) return $classTitle; - + // Expand relative URLs within children of this element // TODO: as it is this is not relative to only children, make this .// and rerun tests $this->resolveChildUrls($e); @@ -630,7 +634,7 @@ class Parser { foreach ($e->childNodes as $node) { $html .= $node->C14N(); } - + return array( 'html' => $html, 'value' => unicodeTrim($this->textContent($e)) @@ -639,7 +643,7 @@ class Parser { /** * Recursively parse microformats - * + * * @param DOMElement $e The element to parse * @return array A representation of the values contained within microformat $e */ @@ -660,26 +664,39 @@ class Parser { foreach ($this->xpath->query('.//*[contains(concat(" ", @class)," h-")]', $e) as $subMF) { // Parse $result = $this->parseH($subMF); - + // If result was already parsed, skip it if (null === $result) continue; + // In most cases, the value attribute of the nested microformat should be the p- parsed value of the elemnt. + // The only times this is different is when the microformat is nested under certain prefixes, which are handled below. $result['value'] = $this->parseP($subMF); // Does this µf have any property names other than h-*? $properties = nestedMfPropertyNamesFromElement($subMF); - + if (!empty($properties)) { // Yes! It’s a nested property µf - foreach ($properties as $property) { - $return[$property][] = $result; + foreach ($properties as $property => $prefixes) { + // Note: handling microformat nesting under multiple conflicting prefixes is not currently specified by the mf2 parsing spec. + $prefixSpecificResult = $result; + if (in_array('p-', $prefixes)) { + $prefixSpecificResult['value'] = $prefixSpecificResult['properties']['name'][0]; + } elseif (in_array('e-', $prefixes)) { + $eParsedResult = $this->parseE($subMF); + $prefixSpecificResult['html'] = $eParsedResult['html']; + $prefixSpecificResult['value'] = $eParsedResult['value']; + } elseif (in_array('u-', $prefixes)) { + $prefixSpecificResult['value'] = $this->parseU($subMF); + } + $return[$property][] = $prefixSpecificResult; } } else { // No, it’s a child µf $children[] = $result; } - + // Make sure this sub-mf won’t get parsed as a µf or property // TODO: Determine if clearing this is required? $this->elementPrefixParsed($subMF, 'h'); @@ -689,19 +706,24 @@ class Parser { $this->elementPrefixParsed($subMF, 'e'); } + if($e->tagName == 'area') { + $coords = $e->getAttribute('coords'); + $shape = $e->getAttribute('shape'); + } + // Handle p-* foreach ($this->xpath->query('.//*[contains(concat(" ", @class) ," p-")]', $e) as $p) { if ($this->isElementParsed($p, 'p')) continue; $pValue = $this->parseP($p); - + // Add the value to the array for it’s p- properties foreach (mfNamesFromElement($p, 'p-') as $propName) { if (!empty($propName)) $return[$propName][] = $pValue; } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($p, 'p'); } @@ -710,32 +732,32 @@ class Parser { foreach ($this->xpath->query('.//*[contains(concat(" ", @class)," u-")]', $e) as $u) { if ($this->isElementParsed($u, 'u')) continue; - + $uValue = $this->parseU($u); - + // Add the value to the array for it’s property types foreach (mfNamesFromElement($u, 'u-') as $propName) { $return[$propName][] = $uValue; } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($u, 'u'); } - + // Handle dt-* foreach ($this->xpath->query('.//*[contains(concat(" ", @class), " dt-")]', $e) as $dt) { if ($this->isElementParsed($dt, 'dt')) continue; - + $dtValue = $this->parseDT($dt, $dates); - + if ($dtValue) { // Add the value to the array for dt- properties foreach (mfNamesFromElement($dt, 'dt-') as $propName) { $return[$propName][] = $dtValue; } } - + // Make sure this sub-mf won’t get parsed as a top level mf $this->elementPrefixParsed($dt, 'dt'); } @@ -762,22 +784,43 @@ class Parser { if (!array_key_exists('name', $return)) { try { // Look for img @alt - if ($e->tagName == 'img' and $e->getAttribute('alt') != '') + if (($e->tagName == 'img' or $e->tagName == 'area') and $e->getAttribute('alt') != '') throw new Exception($e->getAttribute('alt')); - + if ($e->tagName == 'abbr' and $e->hasAttribute('title')) throw new Exception($e->getAttribute('title')); - + // Look for nested img @alt foreach ($this->xpath->query('./img[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { - if ($em->getAttribute('alt') != '') + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { throw new Exception($em->getAttribute('alt')); + } } + // Look for nested area @alt + foreach ($this->xpath->query('./area[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { + throw new Exception($em->getAttribute('alt')); + } + } + + // Look for double nested img @alt foreach ($this->xpath->query('./*[count(preceding-sibling::*)+count(following-sibling::*)=0]/img[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { - if ($em->getAttribute('alt') != '') + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { throw new Exception($em->getAttribute('alt')); + } + } + + // Look for double nested img @alt + foreach ($this->xpath->query('./*[count(preceding-sibling::*)+count(following-sibling::*)=0]/area[count(preceding-sibling::*)+count(following-sibling::*)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames) && $em->getAttribute('alt') != '') { + throw new Exception($em->getAttribute('alt')); + } } throw new Exception($e->nodeValue); @@ -812,36 +855,58 @@ class Parser { // Check for u-url if (!array_key_exists('url', $return)) { // Look for img @src - if ($e->tagName == 'a') + if ($e->tagName == 'a' or $e->tagName == 'area') $url = $e->getAttribute('href'); - - // Look for nested img @src + + // Look for nested a @href foreach ($this->xpath->query('./a[count(preceding-sibling::a)+count(following-sibling::a)=0]', $e) as $em) { - $url = $em->getAttribute('href'); - break; + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames)) { + $url = $em->getAttribute('href'); + break; + } } - + + // Look for nested area @src + foreach ($this->xpath->query('./area[count(preceding-sibling::area)+count(following-sibling::area)=0]', $e) as $em) { + $emNames = mfNamesFromElement($em, 'h-'); + if (empty($emNames)) { + $url = $em->getAttribute('href'); + break; + } + } + if (!empty($url)) $return['url'][] = $this->resolveUrl($url); } // Make sure things are in alphabetical order sort($mfTypes); - + // Phew. Return the final result. $parsed = array( 'type' => $mfTypes, 'properties' => $return ); - if (!empty($children)) + + if (!empty($shape)) { + $parsed['shape'] = $shape; + } + + if (!empty($coords)) { + $parsed['coords'] = $coords; + } + + if (!empty($children)) { $parsed['children'] = array_values(array_filter($children)); + } return $parsed; } - + /** * Parse Rels and Alternatives - * - * Returns [$rels, $alternatives]. If the $rels value is to be empty, i.e. there are no links on the page + * + * Returns [$rels, $alternatives]. If the $rels value is to be empty, i.e. there are no links on the page * with a rel value *not* containing `alternate`, then the type of $rels depends on $this->jsonMode. If set * to true, it will be a stdClass instance, optimising for JSON serialisation. Otherwise (the default case), * it will be an empty array. @@ -849,18 +914,18 @@ class Parser { public function parseRelsAndAlternates() { $rels = array(); $alternates = array(); - + // Iterate through all a, area and link elements with rel attributes foreach ($this->xpath->query('//*[@rel and @href]') as $hyperlink) { if ($hyperlink->getAttribute('rel') == '') continue; - + // Resolve the href $href = $this->resolveUrl($hyperlink->getAttribute('href')); - + // Split up the rel into space-separated values $linkRels = array_filter(explode(' ', $hyperlink->getAttribute('rel'))); - + // If alternate in rels, create alternate structure, append if (in_array('alternate', $linkRels)) { $alt = array( @@ -869,10 +934,19 @@ class Parser { ); if ($hyperlink->hasAttribute('media')) $alt['media'] = $hyperlink->getAttribute('media'); - + if ($hyperlink->hasAttribute('hreflang')) $alt['hreflang'] = $hyperlink->getAttribute('hreflang'); - + + if ($hyperlink->hasAttribute('title')) + $alt['title'] = $hyperlink->getAttribute('title'); + + if ($hyperlink->hasAttribute('type')) + $alt['type'] = $hyperlink->getAttribute('type'); + + if ($hyperlink->nodeValue) + $alt['text'] = $hyperlink->nodeValue; + $alternates[] = $alt; } else { foreach ($linkRels as $rel) { @@ -880,38 +954,38 @@ class Parser { } } } - + if (empty($rels) and $this->jsonMode) { $rels = new stdClass(); } - + return array($rels, $alternates); } - + /** * Kicks off the parsing routine - * + * * If `$htmlSafe` is set, any angle brackets in the results from non e-* properties * will be HTML-encoded, bringing all output to the same level of encoding. - * + * * If a DOMElement is set as the $context, only descendants of that element will * be parsed for microformats. - * + * * @param bool $htmlSafe whether or not to html-encode non e-* properties. Defaults to false * @param DOMElement $context optionally an element from which to parse microformats * @return array An array containing all the µfs found in the current document */ public function parse($convertClassic = true, DOMElement $context = null) { $mfs = array(); - + if ($convertClassic) { $this->convertLegacy(); } - + $mfElements = null === $context ? $this->xpath->query('//*[contains(concat(" ", @class), " h-")]') : $this->xpath->query('.//*[contains(concat(" ", @class), " h-")]', $context); - + // Parser microformats foreach ($mfElements as $node) { // For each microformat @@ -920,64 +994,64 @@ class Parser { // Add the value to the array for this property type $mfs[] = $result; } - + // Parse rels list($rels, $alternates) = $this->parseRelsAndAlternates(); - + $top = array( 'items' => array_values(array_filter($mfs)), 'rels' => $rels ); - + if (count($alternates)) $top['alternates'] = $alternates; - + return $top; } - + /** * Parse From ID - * + * * Given an ID, parse all microformats which are children of the element with * that ID. - * + * * Note that rel values are still document-wide. - * - * If an element with the ID is not found, an empty skeleton mf2 array structure + * + * If an element with the ID is not found, an empty skeleton mf2 array structure * will be returned. - * + * * @param string $id * @param bool $htmlSafe = false whether or not to HTML-encode angle brackets in non e-* properties * @return array */ public function parseFromId($id, $convertClassic=true) { $matches = $this->xpath->query("//*[@id='{$id}']"); - + if (empty($matches)) return array('items' => array(), 'rels' => array(), 'alternates' => array()); - + return $this->parse($convertClassic, $matches->item(0)); } /** * Convert Legacy Classnames - * + * * Adds microformats2 classnames into a document containing only legacy * semantic classnames. - * + * * @return Parser $this */ public function convertLegacy() { $doc = $this->doc; $xp = new DOMXPath($doc); - + // replace all roots foreach ($this->classicRootMap as $old => $new) { foreach ($xp->query('//*[contains(concat(" ", @class, " "), " ' . $old . ' ") and not(contains(concat(" ", @class, " "), " ' . $new . ' "))]') as $el) { $el->setAttribute('class', $el->getAttribute('class') . ' ' . $new); } } - + foreach ($this->classicPropertyMap as $oldRoot => $properties) { $newRoot = $this->classicRootMap[$oldRoot]; foreach ($properties as $old => $new) { @@ -986,16 +1060,16 @@ class Parser { } } } - + return $this; } - + /** * XPath Query - * + * * Runs an XPath query over the current document. Works in exactly the same * way as DOMXPath::query. - * + * * @param string $expression * @param DOMNode $context * @return DOMNodeList @@ -1003,7 +1077,7 @@ class Parser { public function query($expression, $context = null) { return $this->xpath->query($expression, $context); } - + /** * Classic Root Classname map */ @@ -1013,11 +1087,11 @@ class Parser { 'hentry' => 'h-entry', 'hrecipe' => 'h-recipe', 'hresume' => 'h-resume', - 'hevent' => 'h-event', + 'vevent' => 'h-event', 'hreview' => 'h-review', 'hproduct' => 'h-product' ); - + public $classicPropertyMap = array( 'vcard' => array( 'fn' => 'p-name', @@ -1084,7 +1158,7 @@ class Parser { 'skill' => 'p-skill', 'affiliation' => 'p-affiliation h-card', ), - 'hevent' => array( + 'vevent' => array( 'dtstart' => 'dt-start', 'dtend' => 'dt-end', 'duration' => 'dt-duration', @@ -1246,7 +1320,7 @@ function resolveUrl($baseURI, $referenceURI) { # 5.2.3 Merge Paths function mergePaths($base, $reference) { # If the base URI has a defined authority component and an empty - # path, + # path, if($base['authority'] && $base['path'] == null) { # then return a string consisting of "/" concatenated with the # reference's path; otherwise, From 84a65c718939be3c69b99a1100ef05ebee9be93b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 3 Sep 2015 17:52:04 +0200 Subject: [PATCH 096/156] Include PHP libraries from system if not packaged and they are installed. Thanks to: "Bhuvan Krishna" "Sunil Mohan" --- lib/framework.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/framework.php b/lib/framework.php index 1834c3e786..d749d23bdf 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -137,9 +137,18 @@ spl_autoload_register('GNUsocial_class_autoload'); * and is available here: http://www.php-fig.org/psr/psr-0/ */ spl_autoload_register(function($class){ - $file = INSTALLDIR.'/extlib/'.preg_replace('{\\\\|_(?!.*\\\\)}', DIRECTORY_SEPARATOR, ltrim($class, '\\')).'.php'; + $class_path = preg_replace('{\\\\|_(?!.*\\\\)}', DIRECTORY_SEPARATOR, ltrim($class, '\\')).'.php'; + $file = INSTALLDIR.'/extlib/'.$class_path; if (file_exists($file)) { require_once $file; + return; + } + + # Try if the system has this external library + $file = '/usr/share/php/'.$class_path; + if (file_exists($file)) { + require_once $file; + return; } }); From e06553b15b8a6cdc398562ea243a7368b10b373d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 3 Sep 2015 18:04:13 +0200 Subject: [PATCH 097/156] Suggested edits by hannes2peer --- actions/apiaccountupdateprofile.php | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/actions/apiaccountupdateprofile.php b/actions/apiaccountupdateprofile.php index a9842ec5d8..8767dabf85 100644 --- a/actions/apiaccountupdateprofile.php +++ b/actions/apiaccountupdateprofile.php @@ -96,21 +96,12 @@ class ApiAccountUpdateProfileAction extends ApiAuthAction $original = clone($profile); - if (!empty($this->name)) { - $profile->fullname = $this->name; - } - - if (!empty($this->url)) { - $profile->homepage = $this->url; - } - - if (!empty($this->description)) { - $profile->bio = $this->description; - } + $profile->fullname = $this->name; + $profile->homepage = $this->url; + $profile->bio = $this->description; + $profile->location = $this->location; if (!empty($this->location)) { - $profile->location = $this->location; - $loc = Location::fromName($this->location); if (!empty($loc)) { @@ -119,6 +110,12 @@ class ApiAccountUpdateProfileAction extends ApiAuthAction $profile->location_id = $loc->location_id; $profile->location_ns = $loc->location_ns; } + } else { + // location is empty so reset the extrapolated information too + $profile->lat = ''; + $profile->lon = ''; + $profile->location_id = ''; + $profile->location_ns = ''; } $result = $profile->update($original); From 1e07f8c045decda6a1d71f5551dd1357a5af0bc3 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 4 Sep 2015 20:35:11 +0200 Subject: [PATCH 098/156] Incorrect use of DataObject type matching after ->find() --- classes/Conversation.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/classes/Conversation.php b/classes/Conversation.php index 537c214a4c..9ef1e06b76 100644 --- a/classes/Conversation.php +++ b/classes/Conversation.php @@ -110,8 +110,7 @@ class Conversation extends Managed_DataObject { $conv = new Conversation(); $conv->id = $notice->conversation; - $conv->find(true); - if (!$conv instanceof Conversation) { + if (!$conv->find(true)) { common_debug('Conversation does not exist for notice ID: '.$notice->id); throw new NoResultException($conv); } From 476197569f454cd5f37582bff1e0d40097df6ee9 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 4 Sep 2015 22:24:01 +0200 Subject: [PATCH 099/156] sys_get_temp_dir was added in PHP 5 >= 5.2.1 Our requirements are higher than that, so let's just remove the workaround --- extlib/get_temp_dir.php | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 extlib/get_temp_dir.php diff --git a/extlib/get_temp_dir.php b/extlib/get_temp_dir.php deleted file mode 100644 index 4ec96e5221..0000000000 --- a/extlib/get_temp_dir.php +++ /dev/null @@ -1,14 +0,0 @@ - From 3c86542a403ed9f1242487ccc6d230fc7d3547c8 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 4 Sep 2015 22:25:11 +0200 Subject: [PATCH 100/156] Move notice location data to Notice_location Will probably take a _long_ time to do scripts/upgrade.php but don't worry, it can be aborted and resumed. --- classes/Notice.php | 123 ++++++++++++++++++--------- lib/apiaction.php | 26 ++++-- lib/noticelistitem.php | 15 ++-- lib/rss10action.php | 20 +++-- plugins/Mapstraction/actions/map.php | 8 +- 5 files changed, 126 insertions(+), 66 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 050eeaf90d..0d0933115e 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -65,10 +65,6 @@ class Notice extends Managed_DataObject public $is_local; // int(4) public $source; // varchar(32) public $conversation; // int(4) - public $lat; // decimal(10,7) - public $lon; // decimal(10,7) - public $location_id; // int(4) - public $location_ns; // int(4) public $repeat_of; // int(4) public $verb; // varchar(191) not 255 because utf8mb4 takes more space public $object_type; // varchar(191) not 255 because utf8mb4 takes more space @@ -93,10 +89,6 @@ class Notice extends Managed_DataObject 'is_local' => array('type' => 'int', 'size' => 'tiny', 'default' => 0, 'description' => 'notice was generated by a user'), 'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'), 'conversation' => array('type' => 'int', 'description' => 'id of root notice in this conversation'), - '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'), - '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' => 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'), @@ -196,6 +188,7 @@ class Notice extends Managed_DataObject // Clear related records $this->clearReplies(); + $this->clearLocation(); $this->clearRepeats(); $this->clearTags(); $this->clearGroupInboxes(); @@ -621,14 +614,15 @@ class Notice extends Managed_DataObject } } + $notloc = new Notice_location(); if (!empty($lat) && !empty($lon)) { - $notice->lat = $lat; - $notice->lon = $lon; + $notloc->lat = $lat; + $notloc->lon = $lon; } if (!empty($location_ns) && !empty($location_id)) { - $notice->location_id = $location_id; - $notice->location_ns = $location_ns; + $notloc->location_id = $location_id; + $notloc->location_ns = $location_ns; } if (!empty($rendered)) { @@ -667,7 +661,13 @@ class Notice extends Managed_DataObject // XXX: some of these functions write to the DB try { - $notice->insert(); // throws exception on failure + $notice->insert(); // throws exception on failure, if successful we have an ->id + + if (($notloc->lat && $notloc->lon) || ($notloc->location_id && $notloc->location_ns)) { + $notloc->notice_id = $notice->getID(); + $notloc->insert(); // store the notice location if it had any information + } + // If it's not part of a conversation, it's // the beginning of a new conversation. if (empty($notice->conversation)) { @@ -868,15 +868,10 @@ class Notice extends Managed_DataObject } } + $notloc = null; if ($act->context instanceof ActivityContext) { - $location = $act->context->location; - if ($location) { - $stored->lat = $location->lat; - $stored->lon = $location->lon; - if ($location->location_id) { - $stored->location_ns = $location->location_ns; - $stored->location_id = $location->location_id; - } + if ($act->context->location instanceof Location) { + $notloc = Notice_location::fromLocation($act->context->location); } } else { $act->context = new ActivityContext(); @@ -903,6 +898,12 @@ class Notice extends Managed_DataObject try { $stored->insert(); // throws exception on error + + if ($notloc instanceof Notice_location) { + $notloc->notice_id = $stored->getID(); + $notloc->insert(); + } + $orig = clone($stored); // for updating later in this try clause $object = null; @@ -1844,7 +1845,11 @@ class Notice extends Managed_DataObject // This is not a reply to something } - $ctx->location = $this->getLocation(); + try { + $ctx->location = Notice_location::locFromStored($this); + } catch (ServerException $e) { + $ctx->location = null; + } $conv = null; @@ -2090,23 +2095,6 @@ class Notice extends Managed_DataObject return ($contentlimit > 0 && !empty($content) && (mb_strlen($content) > $contentlimit)); } - function getLocation() - { - $location = null; - - if (!empty($this->location_id) && !empty($this->location_ns)) { - $location = Location::fromId($this->location_id, $this->location_ns); - } - - if (is_null($location)) { // no ID, or Location::fromId() failed - if (!empty($this->lat) && !empty($this->lon)) { - $location = Location::fromLatLon($this->lat, $this->lon); - } - } - - return $location; - } - /** * Convenience function for posting a repeat of an existing message. * @@ -2277,6 +2265,16 @@ class Notice extends Managed_DataObject $reply->free(); } + function clearLocation() + { + $loc = new Notice_location(); + $loc->notice_id = $this->id; + + if ($loc->find()) { + $loc->delete(); + } + } + function clearFiles() { $f2p = new File_to_post(); @@ -2885,4 +2883,51 @@ class Notice extends Managed_DataObject $notice->_setReplies($ids); } } + + static public function beforeSchemaUpdate() + { + $table = strtolower(get_called_class()); + $schema = Schema::get(); + $schemadef = $schema->getTableDef($table); + + // 2015-09-04 We move Notice location data to Notice_location + // First we see if we have to do this at all + if (!isset($schemadef['fields']['lat']) + && !isset($schemadef['fields']['lon']) + && !isset($schemadef['fields']['location_id']) + && !isset($schemadef['fields']['location_ns'])) { + // We have already removed the location fields, so no need to migrate. + return; + } + // Then we make sure the Notice_location table is created! + $schema->ensureTable('notice_location', Notice_location::schemaDef()); + + // Then we continue on our road to migration! + echo "\nFound old $table table, moving location data to 'notice_location' table... (this will probably take a LONG time, but can be aborted and continued)"; + + $notice = new Notice(); + $notice->query(sprintf('SELECT id, lat, lon, location_id, location_ns FROM %1$s ' . + 'WHERE lat IS NOT NULL ' . + 'OR lon IS NOT NULL ' . + 'OR location_id IS NOT NULL ' . + 'OR location_ns IS NOT NULL', + $schema->quoteIdentifier($table))); + print "\nFound {$notice->N} notices with location data, inserting"; + while ($notice->fetch()) { + $notloc = Notice_location::getKV('notice_id', $notice->id); + if ($notloc instanceof Notice_location) { + print "-"; + continue; + } + $notloc = new Notice_location(); + $notloc->notice_id = $notice->id; + $notloc->lat= $notice->lat; + $notloc->lon= $notice->lon; + $notloc->location_id= $notice->location_id; + $notloc->location_ns= $notice->location_ns; + $notloc->insert(); + print "."; + } + print "\n"; + } } diff --git a/lib/apiaction.php b/lib/apiaction.php index cd2207b378..fae8f33d0e 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -366,12 +366,13 @@ class ApiAction extends Action $twitter_status['in_reply_to_screen_name'] = ($replier_profile) ? $replier_profile->nickname : null; - if (isset($notice->lat) && isset($notice->lon)) { + try { + $notloc = Notice_location::locFromStored($notice); // This is the format that GeoJSON expects stuff to be in $twitter_status['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notice->lat, - (float) $notice->lon)); - } else { + 'coordinates' => array((float) $notloc->lat, + (float) $notloc->lon)); + } catch (ServerException $e) { $twitter_status['geo'] = null; } @@ -547,13 +548,14 @@ class ApiAction extends Action $entry['pubDate'] = common_date_rfc2822($notice->created); $entry['guid'] = $entry['link']; - if (isset($notice->lat) && isset($notice->lon)) { + try { + $notloc = Notice_location::locFromStored($notice); // This is the format that GeoJSON expects stuff to be in. // showGeoRSS() below uses it for XML output, so we reuse it $entry['geo'] = array('type' => 'Point', - 'coordinates' => array((float) $notice->lat, - (float) $notice->lon)); - } else { + 'coordinates' => array((float) $notloc->lat, + (float) $notloc->lon)); + } catch (ServerException $e) { $entry['geo'] = null; } @@ -997,7 +999,13 @@ class ApiAction extends Action $statuses = array(); if (is_array($notice)) { - $notice = new ArrayWrapper($notice); + //FIXME: make everything calling showJsonTimeline use only Notice objects + common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); } while ($notice->fetch()) { diff --git a/lib/noticelistitem.php b/lib/noticelistitem.php index f25613b9a9..dc171409f4 100644 --- a/lib/noticelistitem.php +++ b/lib/noticelistitem.php @@ -368,18 +368,19 @@ class NoticeListItem extends Widget */ function showNoticeLocation() { - $id = $this->notice->id; - - $location = $this->notice->getLocation(); - - if (empty($location)) { + return; + try { + $location = Notice_location::locFromStored($this->notice); + } catch (NoResultException $e) { + return; + } catch (ServerException $e) { return; } $name = $location->getName(); - $lat = $this->notice->lat; - $lon = $this->notice->lon; + $lat = $location->lat; + $lon = $location->lon; $latlon = (!empty($lat) && !empty($lon)) ? $lat.';'.$lon : ''; if (empty($name)) { diff --git a/lib/rss10action.php b/lib/rss10action.php index 744d1e4b7a..137015530b 100644 --- a/lib/rss10action.php +++ b/lib/rss10action.php @@ -216,15 +216,19 @@ class Rss10Action extends ManagedAction $this->element('dc:creator', null, ($profile->fullname) ? $profile->fullname : $profile->nickname); $this->element('foaf:maker', array('rdf:resource' => $creator_uri)); $this->element('sioc:has_creator', array('rdf:resource' => $creator_uri.'#acct')); - $location = $notice->getLocation(); - if ($location && isset($location->lat) && isset($location->lon)) { - $location_uri = $location->getRdfURL(); - $attrs = array('geo:lat' => $location->lat, - 'geo:long' => $location->lon); - if (strlen($location_uri)) { - $attrs['rdf:resource'] = $location_uri; + try { + $location = Notice_location::locFromStored($notice); + if (isset($location->lat) && isset($location->lon)) { + $location_uri = $location->getRdfURL(); + $attrs = array('geo:lat' => $location->lat, + 'geo:long' => $location->lon); + if (strlen($location_uri)) { + $attrs['rdf:resource'] = $location_uri; + } + $this->element('statusnet:origin', $attrs); } - $this->element('statusnet:origin', $attrs); + } catch (ServerException $e) { + // No result, so no location data } $this->element('statusnet:postIcon', array('rdf:resource' => $profile->avatarUrl())); $this->element('cc:licence', array('rdf:resource' => common_config('license', 'url'))); diff --git a/plugins/Mapstraction/actions/map.php b/plugins/Mapstraction/actions/map.php index be59f5ba0b..48861af994 100644 --- a/plugins/Mapstraction/actions/map.php +++ b/plugins/Mapstraction/actions/map.php @@ -120,9 +120,11 @@ class MapAction extends Action $jsonArray = array(); while ($this->notice->fetch()) { - if (!empty($this->notice->lat) && !empty($this->notice->lon)) { - $jsonNotice = $this->noticeAsJson($this->notice); - $jsonArray[] = $jsonNotice; + try { + $notloc = Notice_location::locFromStored($this->notice); + $jsonArray[] = $this->noticeAsJson($this->notice); + } catch (ServerException $e) { + // no location data } } From 7f30e614027fa27e83ecc32eb5443ee82d03f0f7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 4 Sep 2015 22:30:33 +0200 Subject: [PATCH 101/156] Oops, forgot Notice_location PHP file --- classes/Notice_location.php | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 classes/Notice_location.php diff --git a/classes/Notice_location.php b/classes/Notice_location.php new file mode 100644 index 0000000000..1574414e11 --- /dev/null +++ b/classes/Notice_location.php @@ -0,0 +1,75 @@ + array( + 'notice_id' => array('type' => 'int', 'not null' => true, 'description' => 'notice that is the reply'), + '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'), + 'location_ns' => array('type' => 'int', 'description' => 'namespace for location'), + 'modified' => array('type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'), + ), + 'primary key' => array('notice_id'), + 'foreign keys' => array( + 'notice_location_notice_id_fkey' => array('notice', array('notice_id' => 'id')), + ), + 'indexes' => array( + 'notice_location_location_id_idx' => array('location_id'), + ), + ); + } + + static function locFromStored(Notice $stored) + { + $loc = new Notice_location(); + $loc->notice_id = $stored->getID(); + if (!$loc->find(true)) { + throw new NoResultException($loc); + } + return $loc->asLocation(); + } + + static function fromLocation(Location $location) + { + $notloc = new Notice_location(); + $notloc->lat = $location->lat; + $notloc->lon = $location->lon; + $notloc->location_ns = $location->location_ns; + $notloc->location_id = $location->location_id; + return $notloc; + } + + public function asLocation() + { + $location = null; + + if (!empty($this->location_id) && !empty($this->location_ns)) { + $location = Location::fromId($this->location_id, $this->location_ns); + } + + if (is_null($location)) { // no ID, or Location::fromId() failed + $location = Location::fromLatLon($this->lat, $this->lon); + } + + if (is_null($location)) { + throw new ServerException('Location could not be looked up from existing data.'); + } + + return $location; + } +} From ea75e1c29c083f7c18f2b3e0982b82028e5bf777 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Fri, 4 Sep 2015 23:12:23 +0200 Subject: [PATCH 102/156] Notice_location class in db/core.php --- db/core.php | 1 + 1 file changed, 1 insertion(+) diff --git a/db/core.php b/db/core.php index ec3fe7f736..d779717fd4 100644 --- a/db/core.php +++ b/db/core.php @@ -39,6 +39,7 @@ $classes = array('Schema_version', 'Subscription_queue', 'Oauth_token_association', 'Notice', + 'Notice_location', 'Notice_source', 'Reply', 'Consumer', From 4354ce21d193bc752002c3dec60a8ea38be03c01 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 6 Sep 2015 01:53:11 +0200 Subject: [PATCH 103/156] introducing html_sprintf for easier sprintf'ing with htmlspecialchars --- lib/util.php | 9 +++++++++ plugins/Activity/ActivityPlugin.php | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/util.php b/lib/util.php index 1ff5b13b93..66847a4350 100644 --- a/lib/util.php +++ b/lib/util.php @@ -2429,3 +2429,12 @@ function common_strip_html($html, $trim=true, $save_whitespace=false) $text = html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8'); return $trim ? trim($text) : $text; } + +function html_sprintf() +{ + $args = func_get_args(); + for ($i=1; $i%2$s started following %4$s.'), + $rendered = html_sprintf(_m('%2$s started following %4$s.'), $profile->getUrl(), $profile->getBestName(), $other->getUrl(), @@ -110,7 +110,7 @@ class ActivityPlugin extends Plugin // TRANS: Text for "stopped following" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a profile URL, %4$s is a profile name. - $rendered = sprintf(_m('%2$s stopped following %4$s.'), + $rendered = html_sprintf(_m('%2$s stopped following %4$s.'), $profile->getUrl(), $profile->getBestName(), $other->getUrl(), @@ -155,7 +155,7 @@ class ActivityPlugin extends Plugin // TRANS: Text for "stopped liking" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a notice URL, %4$s is an author name. - $rendered = sprintf(_m('%2$s stopped liking %4$s\'s update.'), + $rendered = html_sprintf(_m('%2$s stopped liking %4$s\'s update.'), $profile->getUrl(), $profile->getBestName(), $notice->getUrl(), @@ -200,7 +200,7 @@ class ActivityPlugin extends Plugin // TRANS: Text for "joined group" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a group URL, %4$s is a group name. - $rendered = sprintf(_m('%2$s joined the group %4$s.'), + $rendered = html_sprintf(_m('%2$s joined the group %4$s.'), $profile->getUrl(), $profile->getBestName(), $group->homeUrl(), @@ -241,7 +241,7 @@ class ActivityPlugin extends Plugin // TRANS: Text for "left group" item in activity plugin. // TRANS: %1$s is a profile URL, %2$s is a profile name, // TRANS: %3$s is a group URL, %4$s is a group name. - $rendered = sprintf(_m('%2$s left the group %4$s.'), + $rendered = html_sprintf(_m('%2$s left the group %4$s.'), $profile->getUrl(), $profile->getBestName(), $group->homeUrl(), From 917a547f918e91244aea1b80dd303626f3cabb18 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 6 Sep 2015 02:01:39 +0200 Subject: [PATCH 104/156] Subscription::saveNew is a static function --- classes/Subscription.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Subscription.php b/classes/Subscription.php index 6601bcd6fc..16bab350d9 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -150,7 +150,7 @@ class Subscription extends Managed_DataObject * Low-level subscription save. * Outside callers should use Subscription::start() */ - protected function saveNew($subscriber_id, $other_id) + protected static function saveNew($subscriber_id, $other_id) { $sub = new Subscription(); From a541533e043ba4e426237d4379ce089ea48a9b26 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 6 Sep 2015 02:04:12 +0200 Subject: [PATCH 105/156] Use Profile objects in Subscription::saveNew --- classes/Subscription.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/Subscription.php b/classes/Subscription.php index 16bab350d9..314a597f55 100644 --- a/classes/Subscription.php +++ b/classes/Subscription.php @@ -101,7 +101,7 @@ class Subscription extends Managed_DataObject $sub = Subscription_queue::getSubQueue($subscriber, $other); } } else { - $sub = self::saveNew($subscriber->id, $other->id); + $sub = self::saveNew($subscriber, $other); $sub->notify(); self::blow('user:notices_with_friends:%d', $subscriber->id); @@ -150,12 +150,12 @@ class Subscription extends Managed_DataObject * Low-level subscription save. * Outside callers should use Subscription::start() */ - protected static function saveNew($subscriber_id, $other_id) + protected static function saveNew(Profile $subscriber, Profile $other) { $sub = new Subscription(); - $sub->subscriber = $subscriber_id; - $sub->subscribed = $other_id; + $sub->subscriber = $subscriber->getID(); + $sub->subscribed = $other->getID(); $sub->jabber = 1; $sub->sms = 1; $sub->created = common_sql_now(); From 4b83d627506486b108cbc42034fa19b7469027f2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 9 Sep 2015 16:29:58 +0200 Subject: [PATCH 106/156] Split classes into their own files --- lib/activitystreamjsondocument.php | 118 ----------------------------- lib/activitystreamslink.php | 35 +++++++++ lib/activitystreamsmedialink.php | 41 ++++++++++ lib/jsonactivitycollection.php | 44 +++++++++++ 4 files changed, 120 insertions(+), 118 deletions(-) create mode 100644 lib/activitystreamslink.php create mode 100644 lib/activitystreamsmedialink.php create mode 100644 lib/jsonactivitycollection.php diff --git a/lib/activitystreamjsondocument.php b/lib/activitystreamjsondocument.php index 0466045fef..12c3882c25 100644 --- a/lib/activitystreamjsondocument.php +++ b/lib/activitystreamjsondocument.php @@ -175,121 +175,3 @@ class ActivityStreamJSONDocument extends JSONActivityCollection } } - -/** - * A class for representing MediaLinks in JSON Activities - * - * @category Feed - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class ActivityStreamsMediaLink extends ActivityStreamsLink -{ - private $linkDict; - - function __construct( - $url = null, - $width = null, - $height = null, - $mediaType = null, // extension - $rel = null, // extension - $duration = null - ) - { - parent::__construct($url, $rel, $mediaType); - $this->linkDict = array( - 'width' => intval($width), - 'height' => intval($height), - 'duration' => intval($duration) - ); - } - - function asArray() - { - return array_merge( - parent::asArray(), - array_filter($this->linkDict) - ); - } -} - -/* - * Collection primarily as the root of an Activity Streams doc but can be used as the value - * of extension properties in a variety of situations. - * - * A valid Collection object serialization MUST contain at least the url or items properties. - */ -class JSONActivityCollection { - - /* Non-negative integer specifying the total number of activities within the stream */ - protected $totalItems; - - /* An array containing a listing of Objects of any object type */ - protected $items; - - /* IRI referencing a JSON document containing the full listing of objects in the collection */ - protected $url; - - /** - * Constructor - * - * @param array $items array of activity items - * @param string $url url of a doc list all the objs in the collection - * @param int $totalItems total number of items in the collection - */ - function __construct($items = null, $url = null) - { - $this->items = empty($items) ? array() : $items; - $this->totalItems = count($items); - $this->url = $url; - } - - /** - * Get the total number of items in the collection - * - * @return int total the total - */ - public function getTotalItems() - { - $this->totalItems = count($items); - return $this->totalItems; - } -} - -/** - * A class for representing links in JSON Activities - * - * @category Feed - * @package StatusNet - * @author Zach Copley - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class ActivityStreamsLink -{ - private $linkDict; - - function __construct($url = null, $rel = null, $mediaType = null) - { - // links MUST have a URL - if (empty($url)) { - throw new Exception('Links must have a URL.'); - } - - $this->linkDict = array( - 'url' => $url, - 'rel' => $rel, // extension - 'type' => $mediaType // extension - ); - } - - function asArray() - { - return array_filter($this->linkDict); - } -} - diff --git a/lib/activitystreamslink.php b/lib/activitystreamslink.php new file mode 100644 index 0000000000..2c91deda0e --- /dev/null +++ b/lib/activitystreamslink.php @@ -0,0 +1,35 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ActivityStreamsLink +{ + private $linkDict; + + function __construct($url = null, $rel = null, $mediaType = null) + { + // links MUST have a URL + if (empty($url)) { + throw new Exception('Links must have a URL.'); + } + + $this->linkDict = array( + 'url' => $url, + 'rel' => $rel, // extension + 'type' => $mediaType // extension + ); + } + + function asArray() + { + return array_filter($this->linkDict); + } +} diff --git a/lib/activitystreamsmedialink.php b/lib/activitystreamsmedialink.php new file mode 100644 index 0000000000..c8612afc85 --- /dev/null +++ b/lib/activitystreamsmedialink.php @@ -0,0 +1,41 @@ + + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ActivityStreamsMediaLink extends ActivityStreamsLink +{ + private $linkDict; + + function __construct( + $url = null, + $width = null, + $height = null, + $mediaType = null, // extension + $rel = null, // extension + $duration = null + ) + { + parent::__construct($url, $rel, $mediaType); + $this->linkDict = array( + 'width' => intval($width), + 'height' => intval($height), + 'duration' => intval($duration) + ); + } + + function asArray() + { + return array_merge( + parent::asArray(), + array_filter($this->linkDict) + ); + } +} diff --git a/lib/jsonactivitycollection.php b/lib/jsonactivitycollection.php new file mode 100644 index 0000000000..1d1b482e04 --- /dev/null +++ b/lib/jsonactivitycollection.php @@ -0,0 +1,44 @@ +items = empty($items) ? array() : $items; + $this->totalItems = count($items); + $this->url = $url; + } + + /** + * Get the total number of items in the collection + * + * @return int total the total + */ + public function getTotalItems() + { + $this->totalItems = count($items); + return $this->totalItems; + } +} From 52de57e2f6aacd1d5f89d4e1617e8a52af598680 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 9 Sep 2015 17:03:52 +0200 Subject: [PATCH 107/156] Let's handle notice dataobjects instead, despite fetching twice from db Actually the original object should be fixed here, but we'll handle the FIXME's later. --- lib/apiaction.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index fae8f33d0e..8c1b4c67e7 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -795,7 +795,13 @@ class ApiAction extends Action 'xmlns:statusnet' => 'http://status.net/schema/api/1/')); if (is_array($notice)) { - $notice = new ArrayWrapper($notice); + //FIXME: make everything calling showJsonTimeline use only Notice objects + common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); } while ($notice->fetch()) { @@ -851,7 +857,13 @@ class ApiAction extends Action $this->element('ttl', null, '40'); if (is_array($notice)) { - $notice = new ArrayWrapper($notice); + //FIXME: make everything calling showJsonTimeline use only Notice objects + common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); } while ($notice->fetch()) { @@ -895,7 +907,13 @@ class ApiAction extends Action $this->element('subtitle', null, $subtitle); if (is_array($notice)) { - $notice = new ArrayWrapper($notice); + //FIXME: make everything calling showJsonTimeline use only Notice objects + common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); + $ids = array(); + foreach ($notice as $n) { + $ids[] = $n->getID(); + } + $notice = Notice::multiGet('id', $ids); } while ($notice->fetch()) { From 404d5781fcf2993c882abc26c1d17dc1f3b37cca Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 9 Sep 2015 17:48:06 +0200 Subject: [PATCH 108/156] Annoying debug messages that were just meant to remind --- lib/apiaction.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index 8c1b4c67e7..3564709e5b 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -796,7 +796,6 @@ class ApiAction extends Action if (is_array($notice)) { //FIXME: make everything calling showJsonTimeline use only Notice objects - common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); $ids = array(); foreach ($notice as $n) { $ids[] = $n->getID(); @@ -858,7 +857,6 @@ class ApiAction extends Action if (is_array($notice)) { //FIXME: make everything calling showJsonTimeline use only Notice objects - common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); $ids = array(); foreach ($notice as $n) { $ids[] = $n->getID(); @@ -908,7 +906,6 @@ class ApiAction extends Action if (is_array($notice)) { //FIXME: make everything calling showJsonTimeline use only Notice objects - common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); $ids = array(); foreach ($notice as $n) { $ids[] = $n->getID(); @@ -1018,7 +1015,6 @@ class ApiAction extends Action if (is_array($notice)) { //FIXME: make everything calling showJsonTimeline use only Notice objects - common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!'); $ids = array(); foreach ($notice as $n) { $ids[] = $n->getID(); From 9ad9b91efb7cdfe83111158219f695ad58b5d946 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 10 Sep 2015 15:27:40 +0200 Subject: [PATCH 109/156] Call it compareVerbs when comparing verbs --- lib/activity.php | 2 +- lib/activityhandlerplugin.php | 6 +++--- lib/activityutils.php | 7 ++++++- plugins/Favorite/FavoritePlugin.php | 8 ++++---- plugins/Favorite/classes/Fave.php | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/activity.php b/lib/activity.php index 2d3930df0d..4db3b9ff4e 100644 --- a/lib/activity.php +++ b/lib/activity.php @@ -244,7 +244,7 @@ class Activity if (!empty($targetEl)) { $this->target = new ActivityObject($targetEl); - } elseif (ActivityUtils::compareTypes($this->verb, array(ActivityVerb::FAVORITE))) { + } elseif (ActivityUtils::compareVerbs($this->verb, array(ActivityVerb::FAVORITE))) { // StatusNet didn't send a 'target' for their Favorite atom entries $this->target = clone($this->objects[0]); } diff --git a/lib/activityhandlerplugin.php b/lib/activityhandlerplugin.php index 529749cc1d..6639e27822 100644 --- a/lib/activityhandlerplugin.php +++ b/lib/activityhandlerplugin.php @@ -104,7 +104,7 @@ abstract class ActivityHandlerPlugin extends Plugin function isMyVerb($verb) { $verb = $verb ?: ActivityVerb::POST; // post is the default verb - return ActivityUtils::compareTypes($verb, $this->verbs()); + return ActivityUtils::compareVerbs($verb, $this->verbs()); } function isMyType($type) { @@ -389,7 +389,7 @@ abstract class ActivityHandlerPlugin extends Plugin } elseif ($target instanceof Profile && $target->isLocal()) { $original = null; // FIXME: Shouldn't favorites show up with a 'target' activityobject? - if (!ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) { + if (!ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) { // If this is not a post, it's a verb targeted at something (such as a Favorite attached to a note) if (!empty($activity->objects[0]->id)) { $activity->context->replyToID = $activity->objects[0]->id; @@ -413,7 +413,7 @@ abstract class ActivityHandlerPlugin extends Plugin $actor = $oactor->localProfile(); // FIXME: will this work in all cases? I made it work for Favorite... - if (ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST))) { + if (ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST))) { $object = $activity->objects[0]; } else { $object = $activity; diff --git a/lib/activityutils.php b/lib/activityutils.php index f3383efda5..ef2e232243 100644 --- a/lib/activityutils.php +++ b/lib/activityutils.php @@ -348,7 +348,7 @@ class ActivityUtils return null; } - static function compareTypes($type, $objects) // this does verbs too! + static function compareTypes($type, $objects) { $type = self::resolveUri($type); foreach ((array)$objects as $object) { @@ -359,6 +359,11 @@ class ActivityUtils return false; } + static function compareVerbs($type, $objects) + { + return self::compareTypes($type, $objects); + } + static function resolveUri($uri, $make_relative=false) { if (empty($uri)) { diff --git a/plugins/Favorite/FavoritePlugin.php b/plugins/Favorite/FavoritePlugin.php index eb73bbe43c..aa6cdc9359 100644 --- a/plugins/Favorite/FavoritePlugin.php +++ b/plugins/Favorite/FavoritePlugin.php @@ -527,8 +527,8 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin $expected_verb = $exists ? ActivityVerb::UNFAVORITE : ActivityVerb::FAVORITE; switch (true) { - case $exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)): - case !$exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)): + case $exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)): + case !$exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)): common_redirect(common_local_url('activityverb', array('id' => $target->getID(), 'verb' => ActivityUtils::resolveUri($expected_verb, true)))); @@ -543,10 +543,10 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped) { switch (true) { - case ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)): + case ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)): Fave::addNew($scoped, $target); break; - case ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)): + case ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)): Fave::removeEntry($scoped, $target); break; default: diff --git a/plugins/Favorite/classes/Fave.php b/plugins/Favorite/classes/Fave.php index ab78478db5..f40efcf8ab 100644 --- a/plugins/Favorite/classes/Fave.php +++ b/plugins/Favorite/classes/Fave.php @@ -357,7 +357,7 @@ class Fave extends Managed_DataObject $target = self::getTargetFromStored($stored); // The following logic was copied from StatusNet's Activity plugin - if (ActivityUtils::compareTypes($target->verb, array(ActivityVerb::POST))) { + if (ActivityUtils::compareVerbs($target->verb, array(ActivityVerb::POST))) { // "I like the thing you posted" $act->objects = $target->asActivity()->objects; } else { From 604ed1fd9a25825aff16a0e7f63279c069881eaa Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 10 Sep 2015 15:55:37 +0200 Subject: [PATCH 110/156] Prepare for ActivityModerationPlugin --- classes/Deleted_notice.php | 15 +++------------ classes/Notice.php | 8 ++++++++ lib/activityverb.php | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/classes/Deleted_notice.php b/classes/Deleted_notice.php index a9167f19a4..23bbea1bab 100644 --- a/classes/Deleted_notice.php +++ b/classes/Deleted_notice.php @@ -17,30 +17,21 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** - * Table Definition for notice + * Table Definition for deleted_notice */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; class Deleted_notice extends Managed_DataObject { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - - public $__table = 'deleted_notice'; // table name + public $__table = 'deleted_notice'; // table name public $id; // int(4) primary_key not_null public $profile_id; // int(4) not_null public $uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space public $created; // datetime() not_null public $deleted; // datetime() not_null - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - public static function schemaDef() { return array( diff --git a/classes/Notice.php b/classes/Notice.php index 0d0933115e..41c4544cf2 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -158,6 +158,14 @@ class Notice extends Managed_DataObject $this->_profile[$this->profile_id] = $profile; } + public function deleteAs(Profile $actor) + { + if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) { + return $this->delete(); + } + throw new AuthorizationException('You are not allowed to delete other user\'s notices'); + } + function delete($useWhere=false) { // For auditing purposes, save a record that the notice diff --git a/lib/activityverb.php b/lib/activityverb.php index dc6f9c93f4..187962d617 100644 --- a/lib/activityverb.php +++ b/lib/activityverb.php @@ -54,9 +54,9 @@ class ActivityVerb const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend'; const JOIN = 'http://activitystrea.ms/schema/1.0/join'; const TAG = 'http://activitystrea.ms/schema/1.0/tag'; + const DELETE = 'delete'; // the url part is not used anymore, and this feature is new enough to avoid problems with legacy nodes if used without http://... // Custom OStatus verbs for the flipside until they're standardized - const DELETE = 'http://ostatus.org/schema/1.0/unfollow'; 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'; From b209dcf8a75dd1ead7fa9ae7df06d9a73d77928d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 15 Sep 2015 12:07:35 +0200 Subject: [PATCH 111/156] DocAction now extends ManagedAction --- actions/doc.php | 72 +++++++------------------------------------------ 1 file changed, 10 insertions(+), 62 deletions(-) diff --git a/actions/doc.php b/actions/doc.php index 85d35d8fcf..e0fd21fcef 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -28,9 +28,7 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } /** * Documentation class. @@ -42,16 +40,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ */ -class DocAction extends Action +class DocAction extends ManagedAction { var $output = null; var $filename = null; var $title = null; - function prepare($args) + protected function doPreparation() { - parent::prepare($args); - $this->title = $this->trimmed('title'); if (!preg_match('/^[a-zA-Z0-9_-]*$/', $this->title)) { $this->title = 'help'; @@ -59,52 +55,6 @@ class DocAction extends Action $this->output = null; $this->loadDoc(); - return true; - } - - /** - * Handle a request - * - * @param array $args array of arguments - * - * @return nothing - */ - function handle($args) - { - parent::handle($args); - $this->showPage(); - } - - /** - * Page title - * - * Gives the page title of the document. Override default for hAtom entry. - * - * @return void - */ - function showPageTitle() - { - $this->element('h1', array('class' => 'entry-title'), $this->title()); - } - - /** - * Block for content. - * - * Overrides default from Action to wrap everything in an hAtom entry. - * - * @return void. - */ - function showContentBlock() - { - $this->elementStart('div', array('id' => 'content', 'class' => 'h-entry')); - $this->showPageTitle(); - $this->showPageNoticeBlock(); - $this->elementStart('div', array('id' => 'content_inner', - 'class' => 'e-content')); - // show the actual content (forms, lists, whatever) - $this->showContent(); - $this->elementEnd('div'); - $this->elementEnd('div'); } /** @@ -119,16 +69,14 @@ class DocAction extends Action $this->raw($this->output); } - /** - * Page title. - * - * Uses the title of the document. - * - * @return page title - */ - function title() + function showNoticeForm() { - return ucfirst($this->title); + // no title + } + + function showPageTitle() + { + // no title } /** From 0bd0c65b876656ff86e7aadd4aae354842f3a92c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 15 Sep 2015 14:26:33 +0200 Subject: [PATCH 112/156] Bring back title to DocAction --- actions/doc.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actions/doc.php b/actions/doc.php index e0fd21fcef..694544dd03 100644 --- a/actions/doc.php +++ b/actions/doc.php @@ -57,6 +57,11 @@ class DocAction extends ManagedAction $this->loadDoc(); } + public function title() + { + return ucfirst($this->title); + } + /** * Display content. * @@ -71,12 +76,7 @@ class DocAction extends ManagedAction function showNoticeForm() { - // no title - } - - function showPageTitle() - { - // no title + // no notice form } /** From 02418cffd81389bd3a7e17daf9eac259ac0b9439 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 27 Sep 2015 11:54:52 +0200 Subject: [PATCH 113/156] Be harsher on database saving failure for File entry --- classes/File.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes/File.php b/classes/File.php index 1a29ea5107..8991616f82 100644 --- a/classes/File.php +++ b/classes/File.php @@ -99,6 +99,10 @@ class File extends Managed_DataObject if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']); if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']); $file_id = $file->insert(); + + if ($file_id === false) { + throw new ServerException('File/URL metadata could not be saved to the database.'); + } } Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url)); From edb73ec48009749f4c607c88947f182a02312ad5 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 27 Sep 2015 12:29:38 +0200 Subject: [PATCH 114/156] Use exception instead of if-statement in File::saveNew --- classes/File.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/classes/File.php b/classes/File.php index 8991616f82..d31141cf8e 100644 --- a/classes/File.php +++ b/classes/File.php @@ -82,14 +82,15 @@ class File extends Managed_DataObject * @param string $given_url * @return File */ - public static function saveNew(array $redir_data, $given_url) { + public static function saveNew(array $redir_data, $given_url) + { + $file = null; - // 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('urlhash', self::hashurl($given_url)); - - if (!$file instanceof File) { + try { + // I don't know why we have to keep doing this but we run a last check to avoid + // uniqueness bugs. + $file = File::getByUrl($given_url); + } catch (NoResultException $e) { $file = new File; $file->urlhash = self::hashurl($given_url); $file->url = $given_url; From 0e2470998936872dca00f7819408933a1e72b4b7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 27 Sep 2015 22:51:11 +0200 Subject: [PATCH 115/156] Profile/Peopletag file splitting for autoload --- actions/showprofiletag.php | 26 +---- lib/framework.php | 1 + lib/peopletag.php | 20 ++++ lib/peopletaglist.php | 206 +-------------------------------- lib/peopletaglistitem.php | 228 +++++++++++++++++++++++++++++++++++++ 5 files changed, 251 insertions(+), 230 deletions(-) create mode 100644 lib/peopletag.php create mode 100644 lib/peopletaglistitem.php diff --git a/actions/showprofiletag.php b/actions/showprofiletag.php index 6ba0c18f90..09085ebf1a 100644 --- a/actions/showprofiletag.php +++ b/actions/showprofiletag.php @@ -22,14 +22,7 @@ * @link http://status.net */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/profileminilist.php'; -require_once INSTALLDIR.'/lib/peopletaglist.php'; -require_once INSTALLDIR.'/lib/noticelist.php'; -require_once INSTALLDIR.'/lib/feedlist.php'; +if (!defined('GNUSOCIAL')) { exit(1); } class ShowprofiletagAction extends Action { @@ -356,20 +349,3 @@ class ShowprofiletagAction extends Action $this->elementEnd('div'); } } - -class Peopletag extends PeopletagListItem -{ - protected $avatarSize = AVATAR_PROFILE_SIZE; - - function showStart() - { - $mode = $this->peopletag->private ? 'private' : 'public'; - $this->out->elementStart('div', array('class' => 'h-entry peopletag peopletag-profile mode-'.$mode, - 'id' => 'peopletag-' . $this->peopletag->id)); - } - - function showEnd() - { - $this->out->elementEnd('div'); - } -} diff --git a/lib/framework.php b/lib/framework.php index d749d23bdf..954e29597f 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -38,6 +38,7 @@ define('PROFILES_PER_PAGE', 20); define('MESSAGES_PER_PAGE', 20); define('GROUPS_PER_PAGE', 20); define('APPS_PER_PAGE', 20); +define('PEOPLETAGS_PER_PAGE', 20); define('GROUPS_PER_MINILIST', 8); define('PROFILES_PER_MINILIST', 8); diff --git a/lib/peopletag.php b/lib/peopletag.php new file mode 100644 index 0000000000..4440beb959 --- /dev/null +++ b/lib/peopletag.php @@ -0,0 +1,20 @@ +peopletag->private ? 'private' : 'public'; + $this->out->elementStart('div', array('class' => 'h-entry peopletag peopletag-profile mode-'.$mode, + 'id' => 'peopletag-' . $this->peopletag->id)); + } + + function showEnd() + { + $this->out->elementEnd('div'); + } +} diff --git a/lib/peopletaglist.php b/lib/peopletaglist.php index b2f2dcca46..3040d41d0a 100644 --- a/lib/peopletaglist.php +++ b/lib/peopletaglist.php @@ -27,13 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR.'/lib/widget.php'; - -define('PEOPLETAGS_PER_PAGE', 20); +if (!defined('GNUSOCIAL')) { exit(1); } /** * Widget to show a list of peopletags @@ -88,201 +82,3 @@ class PeopletagList extends Widget $ptag->show(); } } - -class PeopletagListItem extends Widget -{ - var $peopletag = null; - var $current = null; - var $profile = null; - - /** - * constructor - * - * Also initializes the owner attribute. - * - * @param Notice $notice The notice we'll display - */ - function __construct($peopletag, $current, $out=null) - { - parent::__construct($out); - $this->peopletag = $peopletag; - $this->current = $current; - $this->profile = Profile::getKV('id', $this->peopletag->tagger); - } - - /** - * recipe function for displaying a single peopletag. - * - * This uses all the other methods to correctly display a notice. Override - * it or one of the others to fine-tune the output. - * - * @return void - */ - function url() - { - return $this->peopletag->homeUrl(); - } - - function show() - { - if (empty($this->peopletag)) { - common_log(LOG_WARNING, "Trying to show missing peopletag; skipping."); - return; - } - - if (Event::handle('StartShowPeopletagItem', array($this))) { - $this->showStart(); - $this->showPeopletag(); - $this->showStats(); - $this->showEnd(); - Event::handle('EndShowPeopletagItem', array($this)); - } - } - - function showStart() - { - $mode = ($this->peopletag->private) ? 'private' : 'public'; - $this->out->elementStart('li', array('class' => 'h-entry peopletag mode-' . $mode, - 'id' => 'peopletag-' . $this->peopletag->id)); - } - - function showEnd() - { - $this->out->elementEnd('li'); - } - - function showPeopletag() - { - $this->showCreator(); - $this->showTag(); - $this->showPrivacy(); - $this->showUpdated(); - $this->showActions(); - $this->showDescription(); - } - - function showStats() - { - $this->out->elementStart('div', 'entry-summary entity_statistics'); - $this->out->elementStart('span', 'tagged-count'); - $this->out->element('a', - array('href' => common_local_url('peopletagged', - array('tagger' => $this->profile->nickname, - 'tag' => $this->peopletag->tag))), - // TRANS: Link description for link to list of users tagged with a tag (so part of a list). - _('Listed')); - $this->out->raw($this->peopletag->taggedCount()); - $this->out->elementEnd('span'); - - $this->out->elementStart('span', 'subscriber-count'); - $this->out->element('a', - array('href' => common_local_url('peopletagsubscribers', - array('tagger' => $this->profile->nickname, - 'tag' => $this->peopletag->tag))), - // TRANS: Link description for link to list of users subscribed to a tag. - _('Subscribers')); - $this->out->raw($this->peopletag->subscriberCount()); - $this->out->elementEnd('span'); - $this->out->elementEnd('div'); - } - - function showOwnerOptions() - { - $this->out->elementStart('li', 'entity_edit'); - $this->out->element('a', array('href' => - common_local_url('editpeopletag', array('tagger' => $this->profile->nickname, - 'tag' => $this->peopletag->tag)), - // TRANS: Title for link to edit list settings. - 'title' => _('Edit list settings.')), - // TRANS: Text for link to edit list settings. - _('Edit')); - $this->out->elementEnd('li'); - } - - function showSubscribeForm() - { - $this->out->elementStart('li'); - - if (Event::handle('StartSubscribePeopletagForm', array($this->out, $this->peopletag))) { - if ($this->current) { - if ($this->peopletag->hasSubscriber($this->current->id)) { - $form = new UnsubscribePeopletagForm($this->out, $this->peopletag); - $form->show(); - } else { - $form = new SubscribePeopletagForm($this->out, $this->peopletag); - $form->show(); - } - } - Event::handle('EndSubscribePeopletagForm', array($this->out, $this->peopletag)); - } - - $this->out->elementEnd('li'); - } - - function showCreator() - { - $attrs = array(); - $attrs['href'] = $this->profile->profileurl; - $attrs['class'] = 'h-card p-author nickname p-name'; - $attrs['rel'] = 'contact'; - $attrs['title'] = $this->profile->getFancyName(); - - $this->out->elementStart('a', $attrs); - $this->showAvatar($this->profile); - $this->out->text($this->profile->getNickname()); - $this->out->elementEnd('a'); - } - - function showUpdated() - { - if (!empty($this->peopletag->modified)) { - $this->out->element('abbr', - array('title' => common_date_w3dtf($this->peopletag->modified), - 'class' => 'updated'), - common_date_string($this->peopletag->modified)); - } - } - - function showPrivacy() - { - if ($this->peopletag->private) { - $this->out->elementStart('a', - array('href' => common_local_url('peopletagsbyuser', - array('nickname' => $this->profile->nickname, 'private' => 1)))); - // TRANS: Privacy mode text in list list item for private list. - $this->out->element('span', 'privacy_mode', _m('MODE','Private')); - $this->out->elementEnd('a'); - } - } - - function showTag() - { - $this->out->elementStart('span', 'entry-title tag'); - $this->out->element('a', - array('rel' => 'bookmark', - 'href' => $this->url()), - htmlspecialchars($this->peopletag->tag)); - $this->out->elementEnd('span'); - } - - function showActions() - { - $this->out->elementStart('div', 'entity_actions'); - $this->out->elementStart('ul'); - - if (!$this->peopletag->private) { - $this->showSubscribeForm(); - } - - if (!empty($this->current) && $this->profile->id == $this->current->id) { - $this->showOwnerOptions(); - } - $this->out->elementEnd('ul'); - $this->out->elementEnd('div'); - } - - function showDescription() - { - $this->out->element('div', 'e-content description', $this->peopletag->description); - } -} diff --git a/lib/peopletaglistitem.php b/lib/peopletaglistitem.php new file mode 100644 index 0000000000..75ce60662c --- /dev/null +++ b/lib/peopletaglistitem.php @@ -0,0 +1,228 @@ +. + * + * @category Public + * @package StatusNet + * @author Shashi Gowda + * @copyright 2008-2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class PeopletagListItem extends Widget +{ + var $peopletag = null; + var $current = null; + var $profile = null; + + /** + * constructor + * + * Also initializes the owner attribute. + * + * @param Notice $notice The notice we'll display + */ + function __construct($peopletag, $current, $out=null) + { + parent::__construct($out); + $this->peopletag = $peopletag; + $this->current = $current; + $this->profile = Profile::getKV('id', $this->peopletag->tagger); + } + + /** + * recipe function for displaying a single peopletag. + * + * This uses all the other methods to correctly display a notice. Override + * it or one of the others to fine-tune the output. + * + * @return void + */ + function url() + { + return $this->peopletag->homeUrl(); + } + + function show() + { + if (empty($this->peopletag)) { + common_log(LOG_WARNING, "Trying to show missing peopletag; skipping."); + return; + } + + if (Event::handle('StartShowPeopletagItem', array($this))) { + $this->showStart(); + $this->showPeopletag(); + $this->showStats(); + $this->showEnd(); + Event::handle('EndShowPeopletagItem', array($this)); + } + } + + function showStart() + { + $mode = ($this->peopletag->private) ? 'private' : 'public'; + $this->out->elementStart('li', array('class' => 'h-entry peopletag mode-' . $mode, + 'id' => 'peopletag-' . $this->peopletag->id)); + } + + function showEnd() + { + $this->out->elementEnd('li'); + } + + function showPeopletag() + { + $this->showCreator(); + $this->showTag(); + $this->showPrivacy(); + $this->showUpdated(); + $this->showActions(); + $this->showDescription(); + } + + function showStats() + { + $this->out->elementStart('div', 'entry-summary entity_statistics'); + $this->out->elementStart('span', 'tagged-count'); + $this->out->element('a', + array('href' => common_local_url('peopletagged', + array('tagger' => $this->profile->nickname, + 'tag' => $this->peopletag->tag))), + // TRANS: Link description for link to list of users tagged with a tag (so part of a list). + _('Listed')); + $this->out->raw($this->peopletag->taggedCount()); + $this->out->elementEnd('span'); + + $this->out->elementStart('span', 'subscriber-count'); + $this->out->element('a', + array('href' => common_local_url('peopletagsubscribers', + array('tagger' => $this->profile->nickname, + 'tag' => $this->peopletag->tag))), + // TRANS: Link description for link to list of users subscribed to a tag. + _('Subscribers')); + $this->out->raw($this->peopletag->subscriberCount()); + $this->out->elementEnd('span'); + $this->out->elementEnd('div'); + } + + function showOwnerOptions() + { + $this->out->elementStart('li', 'entity_edit'); + $this->out->element('a', array('href' => + common_local_url('editpeopletag', array('tagger' => $this->profile->nickname, + 'tag' => $this->peopletag->tag)), + // TRANS: Title for link to edit list settings. + 'title' => _('Edit list settings.')), + // TRANS: Text for link to edit list settings. + _('Edit')); + $this->out->elementEnd('li'); + } + + function showSubscribeForm() + { + $this->out->elementStart('li'); + + if (Event::handle('StartSubscribePeopletagForm', array($this->out, $this->peopletag))) { + if ($this->current) { + if ($this->peopletag->hasSubscriber($this->current->id)) { + $form = new UnsubscribePeopletagForm($this->out, $this->peopletag); + $form->show(); + } else { + $form = new SubscribePeopletagForm($this->out, $this->peopletag); + $form->show(); + } + } + Event::handle('EndSubscribePeopletagForm', array($this->out, $this->peopletag)); + } + + $this->out->elementEnd('li'); + } + + function showCreator() + { + $attrs = array(); + $attrs['href'] = $this->profile->profileurl; + $attrs['class'] = 'h-card p-author nickname p-name'; + $attrs['rel'] = 'contact'; + $attrs['title'] = $this->profile->getFancyName(); + + $this->out->elementStart('a', $attrs); + $this->showAvatar($this->profile); + $this->out->text($this->profile->getNickname()); + $this->out->elementEnd('a'); + } + + function showUpdated() + { + if (!empty($this->peopletag->modified)) { + $this->out->element('abbr', + array('title' => common_date_w3dtf($this->peopletag->modified), + 'class' => 'updated'), + common_date_string($this->peopletag->modified)); + } + } + + function showPrivacy() + { + if ($this->peopletag->private) { + $this->out->elementStart('a', + array('href' => common_local_url('peopletagsbyuser', + array('nickname' => $this->profile->nickname, 'private' => 1)))); + // TRANS: Privacy mode text in list list item for private list. + $this->out->element('span', 'privacy_mode', _m('MODE','Private')); + $this->out->elementEnd('a'); + } + } + + function showTag() + { + $this->out->elementStart('span', 'entry-title tag'); + $this->out->element('a', + array('rel' => 'bookmark', + 'href' => $this->url()), + htmlspecialchars($this->peopletag->tag)); + $this->out->elementEnd('span'); + } + + function showActions() + { + $this->out->elementStart('div', 'entity_actions'); + $this->out->elementStart('ul'); + + if (!$this->peopletag->private) { + $this->showSubscribeForm(); + } + + if (!empty($this->current) && $this->profile->id == $this->current->id) { + $this->showOwnerOptions(); + } + $this->out->elementEnd('ul'); + $this->out->elementEnd('div'); + } + + function showDescription() + { + $this->out->element('div', 'e-content description', $this->peopletag->description); + } +} From ad3b62cf2f857d53113692f5a12adce616f17829 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 27 Sep 2015 23:46:30 +0200 Subject: [PATCH 116/156] ShowprofiletagAction now extends ShowstreamAction --- actions/showprofiletag.php | 122 +++++++++---------------------------- actions/showstream.php | 2 +- classes/Profile.php | 14 ++++- classes/Profile_list.php | 21 ++----- lib/listsnav.php | 2 +- lib/noticestreamaction.php | 8 +++ lib/peopletaggroupnav.php | 4 +- lib/profileaction.php | 2 +- lib/router.php | 7 ++- lib/util.php | 2 +- 10 files changed, 63 insertions(+), 121 deletions(-) diff --git a/actions/showprofiletag.php b/actions/showprofiletag.php index 09085ebf1a..f99c9155e4 100644 --- a/actions/showprofiletag.php +++ b/actions/showprofiletag.php @@ -24,88 +24,29 @@ if (!defined('GNUSOCIAL')) { exit(1); } -class ShowprofiletagAction extends Action +class ShowprofiletagAction extends ShowstreamAction { - var $notice, $tagger, $peopletag, $userProfile; + var $notice, $peopletag; - function isReadOnly($args) + protected function doStreamPreparation() { - return true; - } - - function prepare($args) - { - parent::prepare($args); - - if (common_config('singleuser', 'enabled')) { - $tagger_arg = User::singleUserNickname(); - } else { - $tagger_arg = $this->arg('tagger'); - } - $tag_arg = $this->arg('tag'); - $tagger = common_canonical_nickname($tagger_arg); - $tag = common_canonical_tag($tag_arg); - - // Permanent redirect on non-canonical nickname - - if ($tagger_arg != $tagger || $tag_arg != $tag) { - $args = array('tagger' => $nickname, 'tag' => $tag); - if ($this->page != 1) { - $args['page'] = $this->page; - } - common_redirect(common_local_url('showprofiletag', $args), 301); - } - - if (!$tagger) { - // TRANS: Client error displayed when a tagger is expected but not provided. - $this->clientError(_('No tagger.'), 404); - } - - $user = User::getKV('nickname', $tagger); - - if (!$user) { - // TRANS: Client error displayed trying to perform an action related to a non-existing user. - $this->clientError(_('No such user.'), 404); - } - - $this->tagger = $user->getProfile(); - $this->peopletag = Profile_list::pkeyGet(array('tagger' => $user->id, 'tag' => $tag)); - - $current = common_current_user(); - $can_see = !empty($this->peopletag) && (!$this->peopletag->private || - ($this->peopletag->private && $this->peopletag->tagger === $current->id)); - - if (!$can_see) { + $tag = common_canonical_tag($this->arg('tag')); + try { + $this->peopletag = Profile_list::getByPK(array('tagger' => $this->target->getID(), 'tag' => $tag)); + } catch (NoResultException $e) { // TRANS: Client error displayed trying to reference a non-existing list. - $this->clientError(_('No such list.'), 404); + throw new ClientException('No such list.'); } - $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1; - $this->userProfile = Profile::current(); - - $stream = new PeopletagNoticeStream($this->peopletag, $this->userProfile); - - $this->notice = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE, - NOTICES_PER_PAGE + 1); - - if ($this->page > 1 && $this->notice->N == 0) { - // TRANS: Client error when page not found (404). - $this->clientError(_('No such page.'), 404); + if ($this->peopletag->private && !$this->peopletag->getTagger()->sameAs($this->scoped)) { + // TRANS: Client error displayed trying to reference a non-existing list. + throw new AuthorizationException('You do not have permission to see this list.'); } - - return true; } - function handle($args) + public function getStream() { - parent::handle($args); - - if (!$this->peopletag) { - // TRANS: Client error displayed trying to perform an action related to a non-existing user. - $this->clientError(_('No such user.')); - } - - $this->showPage(); + return new PeopletagNoticeStream($this->peopletag, $this->scoped); } function title() @@ -130,7 +71,7 @@ class ShowprofiletagAction extends Action // TRANS: %1$s is a list, %2$s is the tagger's nickname, %3$d is a page number. return sprintf(_('Timeline for %1$s list by %2$s, page %3$d'), $this->peopletag->tag, - $this->tagger->nickname, + $this->target->getNickname(), $this->page ); } else { @@ -153,7 +94,7 @@ class ShowprofiletagAction extends Action // TRANS: %1$s is a list, %2$s is the tagger's nickname. return sprintf(_('Timeline for %1$s list by %2$s'), $this->peopletag->tag, - $this->tagger->nickname + $this->target->getNickname() ); } } @@ -164,29 +105,29 @@ class ShowprofiletagAction extends Action return array(new Feed(Feed::JSON, common_local_url( 'ApiTimelineList', array( - 'user' => $this->tagger->id, + 'user' => $this->target->id, 'id' => $this->peopletag->id, 'format' => 'as' ) ), // TRANS: Feed title. // TRANS: %s is tagger's nickname. - sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->tagger->nickname)), + sprintf(_('Feed for friends of %s (Activity Streams JSON)'), $this->target->getNickname())), new Feed(Feed::RSS2, common_local_url( 'ApiTimelineList', array( - 'user' => $this->tagger->id, + 'user' => $this->target->id, 'id' => $this->peopletag->id, 'format' => 'rss' ) ), // TRANS: Feed title. // TRANS: %s is tagger's nickname. - sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->tagger->nickname)), + sprintf(_('Feed for friends of %s (RSS 2.0)'), $this->target->getNickname())), new Feed(Feed::ATOM, common_local_url( 'ApiTimelineList', array( - 'user' => $this->tagger->id, + 'user' => $this->target->id, 'id' => $this->peopletag->id, 'format' => 'atom' ) @@ -194,7 +135,7 @@ class ShowprofiletagAction extends Action // TRANS: Feed title. // TRANS: %1$s is a list, %2$s is tagger's nickname. sprintf(_('Feed for %1$s list by %2$s (Atom)'), - $this->peopletag->tag, $this->tagger->nickname + $this->peopletag->tag, $this->target->getNickname() ) ) ); @@ -212,11 +153,10 @@ class ShowprofiletagAction extends Action // TRANS: %1$s is a list, %2$s is a tagger's nickname. $message = sprintf(_('This is the timeline for %1$s list by %2$s but no one has posted anything yet.'), $this->peopletag->tag, - $this->tagger->nickname) . ' '; + $this->target->getNickname()) . ' '; if (common_logged_in()) { - $current_user = common_current_user(); - if ($this->tagger->id == $current_user->id) { + if ($this->target->sameAs($this->scoped)) { // TRANS: Additional empty list message for list timeline for currently logged in user tagged tags. $message .= _('Try tagging more people.'); } @@ -231,16 +171,15 @@ class ShowprofiletagAction extends Action $this->elementEnd('div'); } - function showContent() + protected function showContent() { $this->showPeopletag(); - $this->showNotices(); + parent::showContent(); } function showPeopletag() { - $cur = common_current_user(); - $tag = new Peopletag($this->peopletag, $cur, $this); + $tag = new Peopletag($this->peopletag, $this->scoped, $this); $tag->show(); } @@ -260,7 +199,7 @@ class ShowprofiletagAction extends Action $this->page, 'showprofiletag', array('tag' => $this->peopletag->tag, - 'tagger' => $this->tagger->nickname) + 'nickname' => $this->target->getNickname()) ); Event::handle('EndShowProfileTagContent', array($this)); @@ -276,11 +215,6 @@ class ShowprofiletagAction extends Action # $this->showStatistics(); } - function showPageTitle() - { - $this->element('h1', null, $this->title()); - } - function showTagged() { $profile = $this->peopletag->getTagged(0, PROFILES_PER_MINILIST + 1); @@ -307,7 +241,7 @@ class ShowprofiletagAction extends Action if ($cnt > PROFILES_PER_MINILIST) { $this->elementStart('p'); $this->element('a', array('href' => common_local_url('taggedprofiles', - array('nickname' => $this->tagger->nickname, + array('nickname' => $this->target->getNickname(), 'profiletag' => $this->peopletag->tag)), 'class' => 'more'), // TRANS: Link for more "People in list x by a user" diff --git a/actions/showstream.php b/actions/showstream.php index 890c1e711b..650efc7948 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -85,7 +85,7 @@ class ShowstreamAction extends NoticestreamAction } } - function showContent() + protected function showContent() { $this->showNotices(); } diff --git a/classes/Profile.php b/classes/Profile.php index 09f9ca71d1..25a41bee72 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -1593,8 +1593,20 @@ class Profile extends Managed_DataObject return $this; } - public function sameAs(Profile $other) + /** + * Test whether the given profile is the same as the current class, + * for testing identities. + * + * @param Profile $other The other profile, usually from Action's $this->scoped + * + * @return boolean + */ + public function sameAs(Profile $other=null) { + if (is_null($other)) { + // In case $this->scoped is null or something, i.e. not a current/legitimate profile. + return false; + } return $this->getID() === $other->getID(); } diff --git a/classes/Profile_list.php b/classes/Profile_list.php index 2b3d2aa5ad..000e10b41f 100644 --- a/classes/Profile_list.php +++ b/classes/Profile_list.php @@ -21,20 +21,10 @@ * @license GNU Affero General Public License http://www.gnu.org/licenses/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -/** - * Table Definition for profile_list - */ -require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; +if (!defined('GNUSOCIAL')) { exit(1); } class Profile_list extends Managed_DataObject { - ###START_AUTOCODE - /* the code below is auto generated do not remove the above tag */ - public $__table = 'profile_list'; // table name public $id; // int(4) primary_key not_null public $tagger; // int(4) @@ -48,9 +38,6 @@ class Profile_list extends Managed_DataObject public $tagged_count; // smallint public $subscriber_count; // smallint - /* the code above is auto generated do not remove the tag below */ - ###END_AUTOCODE - public static function schemaDef() { return array( @@ -94,7 +81,7 @@ class Profile_list extends Managed_DataObject function getTagger() { - return Profile::getKV('id', $this->tagger); + return Profile::getByID($this->tagger); } /** @@ -145,7 +132,7 @@ class Profile_list extends Managed_DataObject $url = $this->mainpage; } else { $url = common_local_url('showprofiletag', - array('tagger' => $this->getTagger()->nickname, + array('nickname' => $this->getTagger()->nickname, 'tag' => $this->tag)); } } @@ -659,7 +646,7 @@ class Profile_list extends Managed_DataObject $orig = clone($ptag); $user = User::getKV('id', $ptag->tagger); if(!empty($user)) { - $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'tagger' => $user->nickname)); + $ptag->mainpage = common_local_url('showprofiletag', array('tag' => $ptag->tag, 'nickname' => $user->getNickname())); } else { $ptag->mainpage = $uri; // assume this is a remote peopletag and the uri works } diff --git a/lib/listsnav.php b/lib/listsnav.php index a2fa0b8cd1..d550233ef5 100644 --- a/lib/listsnav.php +++ b/lib/listsnav.php @@ -66,7 +66,7 @@ class ListsNav extends MoreMenu while ($this->lists->fetch()) { $mode = $this->lists->private ? 'private' : 'public'; $items[] = array('showprofiletag', - array('tagger' => $this->profile->nickname, + array('nickname' => $this->profile->getNickname(), 'tag' => $this->lists->tag), $this->lists->tag, ''); diff --git a/lib/noticestreamaction.php b/lib/noticestreamaction.php index ed8921860e..bf09b63780 100644 --- a/lib/noticestreamaction.php +++ b/lib/noticestreamaction.php @@ -9,6 +9,9 @@ abstract class NoticestreamAction extends ProfileAction protected function prepare(array $args=array()) { parent::prepare($args); + // In case we need more info than ProfileAction->doPreparation() gives us + $this->doStreamPreparation(); + // fetch the actual stream stuff $stream = $this->getStream(); $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1); @@ -21,6 +24,11 @@ abstract class NoticestreamAction extends ProfileAction return true; } + protected function doStreamPreparation() + { + // pass by default + } + // this fetches the NoticeStream abstract public function getStream(); } diff --git a/lib/peopletaggroupnav.php b/lib/peopletaggroupnav.php index 212a90586b..fb579affe0 100644 --- a/lib/peopletaggroupnav.php +++ b/lib/peopletaggroupnav.php @@ -76,7 +76,7 @@ class PeopletagGroupNav extends Widget { $user = null; - // FIXME: we should probably pass this in + // FIXME: we should probably pass this in and check when PeopletagGroupNav is actually loaded etc. $action = $this->action->trimmed('action'); @@ -107,7 +107,7 @@ class PeopletagGroupNav extends Widget if (Event::handle('StartPeopletagGroupNav', array($this))) { // People tag timeline - $this->out->menuItem(common_local_url('showprofiletag', array('tagger' => $user_profile->nickname, + $this->out->menuItem(common_local_url('showprofiletag', array('nickname' => $user_profile->nickname, 'tag' => $tag->tag)), // TRANS: Menu item in list navigation panel. _m('MENU','List'), diff --git a/lib/profileaction.php b/lib/profileaction.php index 3dc28a7cc4..bdcd575b6b 100644 --- a/lib/profileaction.php +++ b/lib/profileaction.php @@ -315,7 +315,7 @@ abstract class ProfileAction extends ManagedAction $url = $lists->mainpage; } else { $url = common_local_url('showprofiletag', - array('tagger' => $this->target->getNickname(), + array('nickname' => $this->target->getNickname(), 'tag' => $lists->tag)); } if (!$first) { diff --git a/lib/router.php b/lib/router.php index b13c51c328..28ee42662d 100644 --- a/lib/router.php +++ b/lib/router.php @@ -921,6 +921,7 @@ class Router $m->connect('all/:tag', array('action' => 'showprofiletag', + 'nickname' => $nickname, 'tag' => self::REGEX_TAG)); foreach (array('subscriptions', 'subscribers') as $a) { @@ -1003,9 +1004,9 @@ class Router 'tagger_id' => '[0-9]+', 'id' => '[0-9]+')); - $m->connect(':tagger/all/:tag', - array('action' => 'showprofiletag', - 'tagger' => Nickname::DISPLAY_FMT, + $m->connect(':nickname/all/:tag', + array('action' => 'showprofiletag'), + array('nickname' => Nickname::DISPLAY_FMT, 'tag' => self::REGEX_TAG)); foreach (array('subscriptions', 'subscribers') as $a) { diff --git a/lib/util.php b/lib/util.php index 66847a4350..9c37a5e669 100644 --- a/lib/util.php +++ b/lib/util.php @@ -786,7 +786,7 @@ function common_find_mentions($text, Notice $notice) $tagged = $sender->getTaggedSubscribers($tag); $url = common_local_url('showprofiletag', - array('tagger' => $sender->nickname, + array('nickname' => $sender->getNickname(), 'tag' => $tag)); $mentions[] = array('mentioned' => $tagged, From d6e56924fe24a032394bf09b7889280fdc32ed4c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 28 Sep 2015 00:47:16 +0200 Subject: [PATCH 117/156] Accessibility improvement, title for popup close button Apparently it read as "times" in screen readers. --- js/util.js | 2 +- lib/action.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/js/util.js b/js/util.js index cd982bbb17..3e4da433af 100644 --- a/js/util.js +++ b/js/util.js @@ -771,7 +771,7 @@ var SN = { // StatusNet form .addClass('dialogbox') - .append('') + .append('') .closest('.notice-options') .addClass('opaque'); diff --git a/lib/action.php b/lib/action.php index 7f3aea9c1a..4a815c1483 100644 --- a/lib/action.php +++ b/lib/action.php @@ -460,6 +460,7 @@ class Action extends HTMLOutputter // lawsuit // TRANS: Localized tooltip for '...' expansion button on overlong remote messages. $messages['showmore_tooltip'] = _m('TOOLTIP', 'Show more'); + $messages['popup_close_button'] = _m('TOOLTIP', 'Close popup'); $messages = array_merge($messages, $this->getScriptMessages()); From 34b6d3726658627b2979b2a8fab1e45a23f60416 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 28 Sep 2015 00:49:26 +0200 Subject: [PATCH 118/156] Revert "Accessibility fix, make yes button more explicit" This reverts commit 1864a9de382fedf47cc9d8e414440c624d3aebf1. The problem was another label that was missing (fixed in previous commit, the close button) --- plugins/Share/forms/repeat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Share/forms/repeat.php b/plugins/Share/forms/repeat.php index 6e2b486b06..f0ce37fb62 100644 --- a/plugins/Share/forms/repeat.php +++ b/plugins/Share/forms/repeat.php @@ -112,7 +112,7 @@ class RepeatForm extends Form { $this->out->submit('repeat-submit-' . $this->notice->id, // TRANS: Button text to repeat a notice on notice repeat form. - _m('BUTTON','Confirm repeat'), 'submit', null, + _m('BUTTON','Yes'), 'submit', null, // TRANS: Button title to repeat a notice on notice repeat form. _('Repeat this notice.')); } From 118a4f56abb13ef07217e4b9e222fd730c089781 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 28 Sep 2015 12:25:40 +0200 Subject: [PATCH 119/156] Script to test remote oEmbed endpoints --- plugins/Oembed/scripts/poll_oembed.php | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 plugins/Oembed/scripts/poll_oembed.php diff --git a/plugins/Oembed/scripts/poll_oembed.php b/plugins/Oembed/scripts/poll_oembed.php new file mode 100755 index 0000000000..e49e1a50dd --- /dev/null +++ b/plugins/Oembed/scripts/poll_oembed.php @@ -0,0 +1,31 @@ +#!/usr/bin/env php + Date: Mon, 28 Sep 2015 22:36:04 +0200 Subject: [PATCH 120/156] Some conversationRoot lookups failed with deleted notices --- classes/Notice.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 41c4544cf2..13b86ce4c5 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1320,6 +1320,10 @@ class Notice extends Managed_DataObject } } catch (NoParentNoticeException $e) { // Latest notice has no parent + } catch (NoResultException $e) { + // Notice was not found, so we can't go further up in the tree. + // FIXME: Maybe we should do this in a more stable way where deleted + // notices won't break conversation chains? } // No parent, or parent out of scope $root = $last; From a09cf51b99b71010f358924d419100d75ec2ea03 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Tue, 29 Sep 2015 15:17:38 +0200 Subject: [PATCH 121/156] Move Ostatus_profile->processPost function into plugin --- classes/Notice.php | 4 +- lib/activityhandlerplugin.php | 14 +- lib/default.php | 1 + lib/noticelistitem.php | 4 +- .../ActivityVerbPostPlugin.php | 135 +++++++++++++ plugins/Favorite/FavoritePlugin.php | 2 +- plugins/OStatus/classes/Ostatus_profile.php | 188 +----------------- plugins/Share/SharePlugin.php | 2 +- 8 files changed, 151 insertions(+), 199 deletions(-) create mode 100644 plugins/ActivityVerbPost/ActivityVerbPostPlugin.php diff --git a/classes/Notice.php b/classes/Notice.php index 13b86ce4c5..e4d10a5bad 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -866,7 +866,7 @@ class Notice extends Managed_DataObject $conv = Conversation::getKV('uri', $act->context->conversation); if ($conv instanceof Conversation) { common_debug('Conversation stitched together from (probably) a reply activity to unknown remote user. Activity creation time ('.$stored->created.') should maybe be compared to conversation creation time ('.$conv->created.').'); - $stored->conversation = $conv->id; + $stored->conversation = $conv->getID(); } else { // Conversation URI was not found, so we must create it. But we can't create it // until we have a Notice ID because of the database layout... @@ -926,7 +926,7 @@ class Notice extends Managed_DataObject // $act->context->conversation will be null if it was not provided common_debug('Creating a new conversation for stored notice ID='.$stored->getID().' with URI: '.$act->context->conversation); $conv = Conversation::create($stored, $act->context->conversation); - $stored->conversation = $conv->id; + $stored->conversation = $conv->getID(); } $stored->update($orig); diff --git a/lib/activityhandlerplugin.php b/lib/activityhandlerplugin.php index 6639e27822..c403e55a93 100644 --- a/lib/activityhandlerplugin.php +++ b/lib/activityhandlerplugin.php @@ -593,17 +593,9 @@ abstract class ActivityHandlerPlugin extends Plugin protected function showNoticeListItem(NoticeListItem $nli) { - $nli->showNotice(); - $nli->showNoticeAttachments(); - $nli->showNoticeInfo(); - $nli->showNoticeOptions(); - - $nli->showNoticeLink(); - $nli->showNoticeSource(); - $nli->showNoticeLocation(); - $nli->showPermalink(); - - $nli->showNoticeOptions(); + $nli->showNoticeHeaders(); + $nli->showContent(); + $nli->showNoticeFooter(); } public function onStartShowNoticeItemNotice(NoticeListItem $nli) diff --git a/lib/default.php b/lib/default.php index 5bec29cc52..38b8bcb1af 100644 --- a/lib/default.php +++ b/lib/default.php @@ -305,6 +305,7 @@ $default = 'plugins' => array('core' => array( 'ActivityVerb' => array(), + 'ActivityVerbPost' => array(), 'AuthCrypt' => array(), 'Cronish' => array(), 'Favorite' => array(), diff --git a/lib/noticelistitem.php b/lib/noticelistitem.php index dc171409f4..130821de15 100644 --- a/lib/noticelistitem.php +++ b/lib/noticelistitem.php @@ -166,7 +166,7 @@ class NoticeListItem extends Widget function showNoticeTitle() { if (Event::handle('StartShowNoticeTitle', array($this))) { - $this->element('a', array('href' => $this->notice->getUrl(), + $this->element('a', array('href' => $this->notice->getUrl(true), 'class' => 'notice-title'), $this->notice->getTitle()); Event::handle('EndShowNoticeTitle', array($this)); @@ -518,7 +518,7 @@ class NoticeListItem extends Widget } try { $this->out->element('a', - array('href' => $this->notice->getUrl(), + array('href' => $this->notice->getUrl(true), 'class' => $class), // TRANS: Addition in notice list item for single-notice view. _('permalink')); diff --git a/plugins/ActivityVerbPost/ActivityVerbPostPlugin.php b/plugins/ActivityVerbPost/ActivityVerbPostPlugin.php new file mode 100644 index 0000000000..b1abd616c5 --- /dev/null +++ b/plugins/ActivityVerbPost/ActivityVerbPostPlugin.php @@ -0,0 +1,135 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * @package Activity + * @maintainer Mikael Nordfeldth + */ +class ActivityVerbPostPlugin extends ActivityVerbHandlerPlugin +{ + public function tag() + { + return 'post'; + } + + public function types() + { + return array(ActivityObject::ARTICLE, + ActivityObject::BLOGENTRY, + ActivityObject::NOTE, + ActivityObject::STATUS, + ActivityObject::COMMENT, + // null, // if we want to follow the original Ostatus_profile::processActivity code + ); + } + + public function verbs() + { + return array(ActivityVerb::POST); + } + + // FIXME: Set this to abstract public in lib/activityhandlerplugin.php when all plugins have migrated! + protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array()) + { + assert($this->isMyActivity($act)); + + $stored->object_type = ActivityUtils::resolveUri($act->objects[0]->type); + + // We don't have to do just about anything for a new, remote notice since the fields + // are handled in the main Notice::saveActivity function. Such as content, attachments, + // parent/conversation etc. + + // By returning true here instead of something that evaluates + // to false, we show that we have processed everything properly. + return true; + } + + public function activityObjectFromNotice(Notice $notice) + { + $object = new ActivityObject(); + + $object->type = $notice->object_type ?: ActivityObject::NOTE; + $object->id = $notice->getUri(); + $object->title = sprintf('New %1$s by %2$s', ActivityObject::canonicalType($object->type), $notice->getProfile()->getNickname()); + $object->content = $notice->rendered; + $object->link = $notice->getUrl(); + + $object->extra[] = array('status_net', array('notice_id' => $notice->getID())); + + return $object; + } + + public function deleteRelated(Notice $notice) + { + // No action needed as the table for data storage _is_ the notice table. + return true; + } + + + /** + * Command stuff + */ + + // FIXME: Move stuff from lib/command.php to here just as with Share etc. + + + /** + * Layout stuff + */ + + protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null) + { + $out->raw($stored->rendered); + } + + protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // return page title + } + + protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // prepare Action? + } + + protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // handle repeat POST + } + + protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + return new NoticeForm($action, array()); + } + + public function onPluginVersion(array &$versions) + { + $versions[] = array('name' => 'Post verb', + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => 'https://gnu.io/', + 'rawdescription' => + // TRANS: Plugin description. + _m('Post handling with ActivityStreams.')); + + return true; + } +} diff --git a/plugins/Favorite/FavoritePlugin.php b/plugins/Favorite/FavoritePlugin.php index aa6cdc9359..c7a66fe751 100644 --- a/plugins/Favorite/FavoritePlugin.php +++ b/plugins/Favorite/FavoritePlugin.php @@ -332,7 +332,7 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin } } - public function showNoticeListItem(NoticeListItem $nli) + protected function showNoticeListItem(NoticeListItem $nli) { // pass } diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 1c4428b16d..8fc9f6c5f6 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -495,26 +495,7 @@ class Ostatus_profile extends Managed_DataObject if (Event::handle('StartHandleFeedEntryWithProfile', array($activity, $this->localProfile(), &$notice)) && Event::handle('StartHandleFeedEntry', array($activity))) { - switch ($activity->verb) { - case ActivityVerb::POST: - // @todo process all activity objects - switch ($activity->objects[0]->type) { - case ActivityObject::ARTICLE: - case ActivityObject::BLOGENTRY: - case ActivityObject::NOTE: - case ActivityObject::STATUS: - case ActivityObject::COMMENT: - case null: - $notice = $this->processPost($activity, $source); - break; - default: - // TRANS: Client exception. - throw new ClientException(_m('Cannot handle that kind of post.')); - } - break; - default: - common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); - } + common_log(LOG_INFO, "Ignoring activity with unrecognized verb $activity->verb"); Event::handle('EndHandleFeedEntry', array($activity)); Event::handle('EndHandleFeedEntryWithProfile', array($activity, $this, $notice)); @@ -528,178 +509,21 @@ class Ostatus_profile extends Managed_DataObject * @param Activity $activity * @param string $method 'push' or 'salmon' * @return mixed saved Notice or false - * @todo FIXME: Break up this function, it's getting nasty long */ public function processPost($activity, $method) { - $notice = null; + $actor = ActivityUtils::checkAuthorship($activity, $this->localProfile()); - $profile = ActivityUtils::checkAuthorship($activity, $this->localProfile()); - - // It's not always an ActivityObject::NOTE, but... let's just say it is. - - $note = $activity->objects[0]; - - // The id URI will be used as a unique identifier for the notice, - // protecting against duplicate saves. It isn't required to be a URL; - // tag: URIs for instance are found in Google Buzz feeds. - $sourceUri = $note->id; - $dupe = Notice::getKV('uri', $sourceUri); - if ($dupe instanceof Notice) { - common_log(LOG_INFO, "OStatus: ignoring duplicate post: $sourceUri"); - return $dupe; - } - - // We'll also want to save a web link to the original notice, if provided. - $sourceUrl = null; - if ($note->link) { - $sourceUrl = $note->link; - } else if ($activity->link) { - $sourceUrl = $activity->link; - } else if (preg_match('!^https?://!', $note->id)) { - $sourceUrl = $note->id; - } - - // Use summary as fallback for content - - if (!empty($note->content)) { - $sourceContent = $note->content; - } else if (!empty($note->summary)) { - $sourceContent = $note->summary; - } else if (!empty($note->title)) { - $sourceContent = $note->title; - } else { - // @todo FIXME: Fetch from $sourceUrl? - // TRANS: Client exception. %s is a source URI. - throw new ClientException(sprintf(_m('No content for notice %s.'),$sourceUri)); - } - - // Get (safe!) HTML and text versions of the content - - $rendered = common_purify($sourceContent); - $content = common_strip_html($rendered); - - $shortened = common_shorten_links($content); - - // If it's too long, try using the summary, and make the - // HTML an attachment. - - $attachment = null; - - if (Notice::contentTooLong($shortened)) { - $attachment = $this->saveHTMLFile($note->title, $rendered); - $summary = common_strip_html($note->summary); - if (empty($summary)) { - $summary = $content; - } - $shortSummary = common_shorten_links($summary); - if (Notice::contentTooLong($shortSummary)) { - $url = common_shorten_url($sourceUrl); - $shortSummary = substr($shortSummary, - 0, - Notice::maxContent() - (mb_strlen($url) + 2)); - $content = $shortSummary . ' ' . $url; - - // We mark up the attachment link specially for the HTML output - // so we can fold-out the full version inline. - - // @todo FIXME i18n: This tooltip will be saved with the site's default language - // TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime - // TRANS: this will usually be replaced with localised text from StatusNet core messages. - $showMoreText = _m('Show more'); - $attachUrl = common_local_url('attachment', - array('attachment' => $attachment->id)); - $rendered = common_render_text($shortSummary) . - '' . - '…' . - ''; - } - } - - $options = array('is_local' => Notice::REMOTE, - 'url' => $sourceUrl, - 'uri' => $sourceUri, - 'rendered' => $rendered, - 'replies' => array(), - 'groups' => array(), - 'peopletags' => array(), - 'tags' => array(), - 'urls' => array()); - - // Check for optional attributes... - - if (!empty($activity->time)) { - $options['created'] = common_sql_date($activity->time); - } - - if ($activity->context) { - // TODO: context->attention - list($options['groups'], $options['replies']) - = self::filterAttention($profile, $activity->context->attention); - - // Maintain direct reply associations - // @todo FIXME: What about conversation ID? - if (!empty($activity->context->replyToID)) { - $orig = Notice::getKV('uri', $activity->context->replyToID); - if ($orig instanceof Notice) { - $options['reply_to'] = $orig->id; - } - } - if (!empty($activity->context->conversation)) { - // we store the URI here, Notice class can look it up later - $options['conversation'] = $activity->context->conversation; - } - - $location = $activity->context->location; - if ($location) { - $options['lat'] = $location->lat; - $options['lon'] = $location->lon; - if ($location->location_id) { - $options['location_ns'] = $location->location_ns; - $options['location_id'] = $location->location_id; - } - } - } - - if ($this->isPeopletag()) { - $options['peopletags'][] = $this->localPeopletag(); - } - - // Atom categories <-> hashtags - foreach ($activity->categories as $cat) { - if ($cat->term) { - $term = common_canonical_tag($cat->term); - if ($term) { - $options['tags'][] = $term; - } - } - } - - // Atom enclosures -> attachment URLs - foreach ($activity->enclosures as $href) { - // @todo FIXME: Save these locally or....? - $options['urls'][] = $href; - } + $options = array('is_local' => Notice::REMOTE); try { - $saved = Notice::saveNew($profile->id, - $content, - 'ostatus', - $options); - if ($saved instanceof Notice) { - Ostatus_source::saveNew($saved, $this, $method); - if ($attachment instanceof File) { - File_to_post::processNew($attachment, $saved); - } - } + $stored = Notice::saveActivity($activity, $actor, $options); + Ostatus_source::saveNew($stored, $this, $method); } catch (Exception $e) { common_log(LOG_ERR, "OStatus save of remote message $sourceUri failed: " . $e->getMessage()); throw $e; } - common_log(LOG_INFO, "OStatus saved remote message $sourceUri as notice id $saved->id"); - return $saved; + return $stored; } /** diff --git a/plugins/Share/SharePlugin.php b/plugins/Share/SharePlugin.php index c337efbaec..70e1395792 100644 --- a/plugins/Share/SharePlugin.php +++ b/plugins/Share/SharePlugin.php @@ -256,7 +256,7 @@ class SharePlugin extends ActivityVerbHandlerPlugin } } - public function showNoticeListItem(NoticeListItem $nli) + protected function showNoticeListItem(NoticeListItem $nli) { // pass } From 3a6c98ff164e5c77c5ced29750e2259c815ef0b4 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Wed, 30 Sep 2015 22:55:06 +0200 Subject: [PATCH 122/156] NoResultException is common if reply_to was not cleared on parent deletion --- classes/Notice.php | 4 ++++ lib/apiaction.php | 3 +++ lib/implugin.php | 7 +++++-- lib/util.php | 2 ++ plugins/Xmpp/XmppPlugin.php | 3 +++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index e4d10a5bad..44d5adb1b7 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1630,6 +1630,8 @@ class Notice extends Managed_DataObject self::blow('reply:stream:%d', $parentauthor->id); } catch (NoParentNoticeException $e) { // Not a reply, since it has no parent! + } catch (NoResultException $e) { + // Parent notice was probably deleted } // @todo ideally this parser information would only @@ -1855,6 +1857,8 @@ class Notice extends Managed_DataObject $ctx->replyToUrl = $reply->getUrl(true); // true for fallback to local URL, less messy } catch (NoParentNoticeException $e) { // This is not a reply to something + } catch (NoResultException $e) { + // Parent notice was probably deleted } try { diff --git a/lib/apiaction.php b/lib/apiaction.php index 3564709e5b..1bd80ad122 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -330,6 +330,9 @@ class ApiAction extends Action $in_reply_to = $parent->id; } catch (NoParentNoticeException $e) { $in_reply_to = null; + } catch (NoResultException $e) { + // the in_reply_to message has probably been deleted + $in_reply_to = null; } $twitter_status['in_reply_to_status_id'] = $in_reply_to; diff --git a/lib/implugin.php b/lib/implugin.php index 742147dbbd..457c9dba52 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -364,13 +364,16 @@ abstract class ImPlugin extends Plugin protected function formatNotice(Notice $notice) { $profile = $notice->getProfile(); + $nicknames = $profile->getNickname(); try { $parent = $notice->getParent(); $orig_profile = $parent->getProfile(); - $nicknames = sprintf('%1$s => %2$s', $profile->nickname, $orig_profile->nickname); + $nicknames = sprintf('%1$s => %2$s', $profile->getNickname(), $orig_profile->getNickname()); } catch (NoParentNoticeException $e) { - $nicknames = $profile->nickname; + // Not a reply, no parent notice stored + } catch (NoResultException $e) { + // Parent notice was probably deleted } return sprintf('%1$s: %2$s [%3$u]', $nicknames, $notice->content, $notice->id); diff --git a/lib/util.php b/lib/util.php index 9c37a5e669..a5acd8d86b 100644 --- a/lib/util.php +++ b/lib/util.php @@ -725,6 +725,8 @@ function common_find_mentions($text, Notice $notice) } } catch (NoParentNoticeException $e) { // It wasn't a reply to anything, so we can't harvest nickname-relations. + } catch (NoResultException $e) { + // The parent notice was deleted. } $matches = common_find_mentions_raw($text); diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index 04101a8e2b..8c700bae8b 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -351,6 +351,9 @@ class XmppPlugin extends ImPlugin $xs->text(sprintf(' => %s', $orig_profile->nickname)); } catch (NoParentNoticeException $e) { $xs->text(": "); + } catch (NoResultException $e) { + // Parent notice was probably deleted. + $xs->text(": "); } if (!empty($notice->rendered)) { $notice->rendered = str_replace("\t", "", $notice->rendered); From 5b7deee0cc90839bbbc133a0c2c8a0019d32aba9 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 1 Oct 2015 21:40:05 +0200 Subject: [PATCH 123/156] InlineAttachmentListItem put into its own file --- lib/inlineattachmentlist.php | 36 +------------------ lib/inlineattachmentlistitem.php | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 lib/inlineattachmentlistitem.php diff --git a/lib/inlineattachmentlist.php b/lib/inlineattachmentlist.php index 410b3b838d..40ec114ad8 100644 --- a/lib/inlineattachmentlist.php +++ b/lib/inlineattachmentlist.php @@ -27,9 +27,7 @@ * @link http://status.net/ */ -if (!defined('STATUSNET')) { - exit(1); -} +if (!defined('GNUSOCIAL')) { exit(1); } class InlineAttachmentList extends AttachmentList { @@ -51,35 +49,3 @@ class InlineAttachmentList extends AttachmentList return new InlineAttachmentListItem($attachment, $this->out); } } - -class InlineAttachmentListItem extends AttachmentListItem -{ - function showLink() { - $this->out->element('a', $this->linkAttr(), $this->title()); - $this->showRepresentation(); - } - - /** - * start a single notice. - * - * @return void - */ - function showStart() - { - // XXX: RDFa - // TODO: add notice_type class e.g., notice_video, notice_image - $this->out->elementStart('li', array('class' => 'inline-attachment')); - } - - /** - * finish the notice - * - * Close the last elements in the notice list item - * - * @return void - */ - function showEnd() - { - $this->out->elementEnd('li'); - } -} diff --git a/lib/inlineattachmentlistitem.php b/lib/inlineattachmentlistitem.php new file mode 100644 index 0000000000..10b9db202b --- /dev/null +++ b/lib/inlineattachmentlistitem.php @@ -0,0 +1,62 @@ +. + * + * @category UI + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +class InlineAttachmentListItem extends AttachmentListItem +{ + function showLink() { + $this->out->element('a', $this->linkAttr(), $this->title()); + $this->showRepresentation(); + } + + /** + * start a single notice. + * + * @return void + */ + function showStart() + { + // XXX: RDFa + // TODO: add notice_type class e.g., notice_video, notice_image + $this->out->elementStart('li', array('class' => 'inline-attachment')); + } + + /** + * finish the notice + * + * Close the last elements in the notice list item + * + * @return void + */ + function showEnd() + { + $this->out->elementEnd('li'); + } +} From 80bc7f0e2593a3fcfd6a10ba0d71ddfa6ff49e50 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 1 Oct 2015 22:14:49 +0200 Subject: [PATCH 124/156] File handling changes for better logic Also prepares for StoreRemoteMediaPlugin, coming up... --- classes/File.php | 35 ++++++++++++++++++++--------------- lib/attachmentlist.php | 3 +-- lib/imagefile.php | 2 +- lib/util.php | 1 + 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/classes/File.php b/classes/File.php index d31141cf8e..977c02bce6 100644 --- a/classes/File.php +++ b/classes/File.php @@ -85,29 +85,34 @@ class File extends Managed_DataObject public static function saveNew(array $redir_data, $given_url) { $file = null; - try { // I don't know why we have to keep doing this but we run a last check to avoid // uniqueness bugs. $file = File::getByUrl($given_url); + return $file; } catch (NoResultException $e) { - $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']; - if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type']; - if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']); - if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']); - $file_id = $file->insert(); + // We don't have the file's URL since before, so let's continue. + } - if ($file_id === false) { - throw new ServerException('File/URL metadata could not be saved to the database.'); - } + if (!Event::handle('StartFileSaveNew', array(&$redir_data, $given_url))) { + throw new ServerException('File not saved due to an aborted StartFileSaveNew event.'); + } + + $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']; + if (!empty($redir_data['type'])) $file->mimetype = $redir_data['type']; + if (!empty($redir_data['size'])) $file->size = intval($redir_data['size']); + if (isset($redir_data['time']) && $redir_data['time'] > 0) $file->date = intval($redir_data['time']); + $file_id = $file->insert(); + + if ($file_id === false) { + throw new ServerException('File/URL metadata could not be saved to the database.'); } Event::handle('EndFileSaveNew', array($file, $redir_data, $given_url)); - assert ($file instanceof File); return $file; } @@ -476,7 +481,7 @@ class File extends Managed_DataObject /** * @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) + static public function getByHash($hashstr) { $file = new File(); $file->filehash = strtolower($hashstr); diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index d6cfda6f95..dcae917be3 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -58,7 +58,7 @@ class AttachmentList extends Widget * * @param Notice $notice stream of notices from DB_DataObject */ - function __construct($notice, $out=null) + function __construct(Notice $notice, $out=null) { parent::__construct($out); $this->notice = $notice; @@ -75,7 +75,6 @@ class AttachmentList extends Widget function show() { $attachments = $this->notice->attachments(); - $representable = false; foreach ($attachments as $key=>$att) { // Only show attachments representable with a title if ($att->getTitle() === null) { diff --git a/lib/imagefile.php b/lib/imagefile.php index 68cfea48e7..7107487737 100644 --- a/lib/imagefile.php +++ b/lib/imagefile.php @@ -125,7 +125,7 @@ class ImageFile $imgPath = null; $media = common_get_mime_media($file->mimetype); if (Event::handle('CreateFileImageThumbnailSource', array($file, &$imgPath, $media))) { - if (empty($file->filename)) { + if (empty($file->filename) && !file_exists($imgPath)) { throw new UnsupportedMediaException(_('File without filename could not get a thumbnail source.')); } diff --git a/lib/util.php b/lib/util.php index a5acd8d86b..edada03864 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1868,6 +1868,7 @@ function common_get_mime_media($type) return strtolower($tmp[0]); } +// Get only the mimetype and not additional info (separated from bare mime with semi-colon) function common_bare_mime($mimetype) { $mimetype = mb_strtolower($mimetype); From d52b7e3124bb6de1716c58903436849f4ace1e06 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 1 Oct 2015 22:18:33 +0200 Subject: [PATCH 125/156] Oembed fiddling, nothing major --- plugins/Oembed/OembedPlugin.php | 18 ++++++++++-------- plugins/Oembed/classes/File_oembed.php | 16 ++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/plugins/Oembed/OembedPlugin.php b/plugins/Oembed/OembedPlugin.php index c014b65cfb..b5646ab6fc 100644 --- a/plugins/Oembed/OembedPlugin.php +++ b/plugins/Oembed/OembedPlugin.php @@ -99,8 +99,8 @@ class OembedPlugin extends Plugin { $fo = File_oembed::getKV('file_id', $file->id); if ($fo instanceof File_oembed) { - common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file $file_id", __FILE__); - return true; + common_log(LOG_WARNING, "Strangely, a File_oembed object exists for new file {$file->id}", __FILE__); + return true; } if (isset($redir_data['oembed']['json']) @@ -173,10 +173,12 @@ class OembedPlugin extends Plugin public function onStartShowAttachmentRepresentation(HTMLOutputter $out, File $file) { - $oembed = File_oembed::getKV('file_id', $file->id); - if (empty($oembed->type)) { + try { + $oembed = File_oembed::getByFile($file); + } catch (NoResultException $e) { return true; } + switch ($oembed->type) { case 'rich': case 'video': @@ -209,14 +211,14 @@ class OembedPlugin extends Plugin } // All our remote Oembed images lack a local filename property in the File object - if ($file->filename !== null) { + if (!is_null($file->filename)) { return true; } try { // If we have proper oEmbed data, there should be an entry in the File_oembed // and File_thumbnail tables respectively. If not, we're not going to do anything. - $file_oembed = File_oembed::byFile($file); + $file_oembed = File_oembed::getByFile($file); $thumbnail = File_thumbnail::byFile($file); } catch (Exception $e) { // Not Oembed data, or at least nothing we either can or want to use. @@ -274,8 +276,8 @@ class OembedPlugin extends Plugin throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)')); } - // We'll trust sha256 not to have collision issues any time soon :) - $filename = hash('sha256', $imgData) . '.' . common_supported_mime_to_ext($info['mime']); + // We'll trust sha256 (File::FILEHASH_ALG) not to have collision issues any time soon :) + $filename = hash(File::FILEHASH_ALG, $imgData) . '.' . common_supported_mime_to_ext($info['mime']); $fullpath = File_thumbnail::path($filename); // Write the file to disk. Throw Exception on failure if (!file_exists($fullpath) && file_put_contents($fullpath, $imgData) === false) { diff --git a/plugins/Oembed/classes/File_oembed.php b/plugins/Oembed/classes/File_oembed.php index 0e84c6dca8..07629b6ab9 100644 --- a/plugins/Oembed/classes/File_oembed.php +++ b/plugins/Oembed/classes/File_oembed.php @@ -83,12 +83,13 @@ class File_oembed extends Managed_DataObject /** * Fetch an entry by using a File's id */ - static function byFile(File $file) { - $file_oembed = self::getKV('file_id', $file->id); - if (!$file_oembed instanceof File_oembed) { - throw new ServerException(sprintf('No File_oembed entry for File id==%u', $file->id)); + static function getByFile(File $file) { + $fo = new File_oembed(); + $fo->file_id = $file->id; + if (!$fo->find(true)) { + throw new NoResultException($fo); } - return $file_oembed; + return $fo; } public function getUrl() @@ -137,7 +138,10 @@ class File_oembed extends Managed_DataObject } } } - $file_oembed->insert(); + $result = $file_oembed->insert(); + if ($result === false) { + throw new ServerException('Failed to insert File_oembed data into database!'); + } if (!empty($data->thumbnail_url) || ($data->type == 'photo')) { $ft = File_thumbnail::getKV('file_id', $file_id); if ($ft instanceof File_thumbnail) { From b8f52965a9cd2403ab3e063910abb1115d998b4f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Thu, 1 Oct 2015 22:18:47 +0200 Subject: [PATCH 126/156] StoreRemoteMedia plugin to enable locally served thumbs of remote images Add addPlugin('StoreRemoteMedia'); in your config.php to enable. --- .../StoreRemoteMediaPlugin.php | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php diff --git a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php new file mode 100644 index 0000000000..7d50a4502a --- /dev/null +++ b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php @@ -0,0 +1,154 @@ +'value', ...)); + // WARNING, these are _regexps_ (slashes added later). Always escape your dots and end your strings + public $domain_whitelist = array( // hostname => service provider + '^i\d*\.ytimg\.com$' => 'YouTube', + '^i\d*\.vimeocdn\.com$' => 'Vimeo', + ); + public $append_whitelist = array(); // fill this array as domain_whitelist to add more trusted sources + public $check_whitelist = false; // security/abuse precaution + + protected $imgData = array(); + + // these should be declared protected everywhere + public function initialize() + { + parent::initialize(); + + $this->domain_whitelist = array_merge($this->domain_whitelist, $this->append_whitelist); + } + + /** + * Save embedding information for a File, if applicable. + * + * Normally this event is called through File::saveNew() + * + * @param File $file The newly inserted File object. + * @param array $redir_data lookup data eg from File_redirection::where() + * @param string $given_url + * + * @return boolean success + */ + public function onStartFileSaveNew(array &$redir_data, $given_url) + { + // save given URL as title if it's a media file this plugin understands + // which will make it shown in the AttachmentList widgets + + if (isset($redir_data['title']) && strlen($redir_data['title']>0)) { + // Title is already set + return true; + } + if (!isset($redir_data['type'])) { + // Unknown mimetype, it's not our job to figure out what it is. + return true; + } + switch (common_get_mime_media($redir_data['type'])) { + case 'image': + // Just to set something for now at least... + $redir_data['title'] = $given_url; + break; + } + + return true; + } + + public function onCreateFileImageThumbnailSource(File $file, &$imgPath, $media=null) + { + // If we are on a private node, we won't do any remote calls (just as a precaution until + // we can configure this from config.php for the private nodes) + if (common_config('site', 'private')) { + return true; + } + + if ($media !== 'image') { + return true; + } + + // If there is a local filename, it is either a local file already or has already been downloaded. + if (!empty($file->filename)) { + return true; + } + + $this->checkWhitelist($url); + + // First we download the file to memory and test whether it's actually an image file + $imgData = HTTPClient::quickGet($file->getUrl()); + common_debug(sprintf('Downloading remote file id==%u with URL: %s', $file->id, $file->url)); + $info = @getimagesizefromstring($imgData); + if ($info === false) { + throw new UnsupportedMediaException(_('Remote file format was not identified as an image.'), $url); + } elseif (!$info[0] || !$info[1]) { + throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)')); + } + + $filehash = hash(File::FILEHASH_ALG, $imgData); + try { + // Exception will be thrown before $file is set to anything, so old $file value will be kept + $file = File::getByHash($filehash); + + //FIXME: Add some code so we don't have to store duplicate File rows for same hash files. + } catch (NoResultException $e) { + $filename = $filehash . '.' . common_supported_mime_to_ext($info['mime']); + $fullpath = File::path($filename); + + // Write the file to disk if it doesn't exist yet. Throw Exception on failure. + if (!file_exists($fullpath) && file_put_contents($fullpath, $imgData) === false) { + throw new ServerException(_('Could not write downloaded file to disk.')); + } + + // Updated our database for the file record + $orig = clone($file); + $file->filehash = $filehash; + $file->filename = $filename; + $file->width = $info[0]; // array indexes documented on php.net: + $file->height = $info[1]; // https://php.net/manual/en/function.getimagesize.php + // Throws exception on failure. + $file->updateWithKeys($orig, 'id'); + } + // Get rid of the file from memory + unset($imgData); + + $imgPath = $file->getPath(); + + return false; + } + + /** + * @return boolean false on no check made, provider name on success + * @throws ServerException if check is made but fails + */ + protected function checkWhitelist($url) + { + if (!$this->check_whitelist) { + return false; // indicates "no check made" + } + + $host = parse_url($url, PHP_URL_HOST); + foreach ($this->domain_whitelist as $regex => $provider) { + if (preg_match("/$regex/", $host)) { + return $provider; // we trust this source, return provider name + } + } + + throw new ServerException(sprintf(_('Domain not in remote source whitelist: %s'), $host)); + } + + public function onPluginVersion(array &$versions) + { + $versions[] = array('name' => 'StoreRemoteMedia', + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => 'https://gnu.io/', + 'description' => + // TRANS: Plugin description. + _m('Plugin for downloading remotely attached files to local server.')); + return true; + } +} From 0c6fe78a73fddf73145befa63161e58ce29263c2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 00:43:34 +0200 Subject: [PATCH 127/156] Shorthand function for getActor --- plugins/Favorite/classes/Fave.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/Favorite/classes/Fave.php b/plugins/Favorite/classes/Fave.php index f40efcf8ab..1e011decf8 100644 --- a/plugins/Favorite/classes/Fave.php +++ b/plugins/Favorite/classes/Fave.php @@ -423,12 +423,7 @@ class Fave extends Managed_DataObject public function getActor() { - $profile = new Profile(); - $profile->id = $this->user_id; - if (!$profile->find(true)) { - throw new NoResultException($profile); - } - return $profile; + return Profile::getByID($this->user_id); } public function getActorObject() From ae73baf4ee524bfea97a3c43c61b5fea92df0f7f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 01:47:40 +0200 Subject: [PATCH 128/156] Undefined variable use --- plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php index 7d50a4502a..798d7bc79f 100644 --- a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php +++ b/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php @@ -76,7 +76,7 @@ class StoreRemoteMediaPlugin extends Plugin return true; } - $this->checkWhitelist($url); + $this->checkWhitelist($file->getUrl()); // First we download the file to memory and test whether it's actually an image file $imgData = HTTPClient::quickGet($file->getUrl()); From 88f7bb1ed5faded5563eeb1d75d5fb44126b0712 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 02:02:37 +0200 Subject: [PATCH 129/156] Some work on ActivityModeration with notice deletion Let's now create an event called DeleteNotice and also make sure we handle the onNoticeDeleteRelated properly in ActivityModeration to avoid possible endless loops etc. --- classes/Deleted_notice.php | 54 ----- classes/Notice.php | 27 +-- db/core.php | 1 - lib/default.php | 1 + .../ActivityModerationPlugin.php | 118 +++++++++ .../classes/Deleted_notice.php | 224 ++++++++++++++++++ .../ActivityModeration/forms/deletenotice.php | 0 scripts/upgrade.php | 1 + 8 files changed, 346 insertions(+), 80 deletions(-) delete mode 100644 classes/Deleted_notice.php create mode 100644 plugins/ActivityModeration/ActivityModerationPlugin.php create mode 100644 plugins/ActivityModeration/classes/Deleted_notice.php rename lib/deletenoticeform.php => plugins/ActivityModeration/forms/deletenotice.php (100%) diff --git a/classes/Deleted_notice.php b/classes/Deleted_notice.php deleted file mode 100644 index 23bbea1bab..0000000000 --- a/classes/Deleted_notice.php +++ /dev/null @@ -1,54 +0,0 @@ -. - */ - -if (!defined('GNUSOCIAL')) { exit(1); } - -/** - * Table Definition for deleted_notice - */ - -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(191) unique_key not 255 because utf8mb4 takes more space - public $created; // datetime() not_null - public $deleted; // datetime() not_null - - public static function schemaDef() - { - return array( - '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' => 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'), - ), - 'primary key' => array('id'), - 'unique keys' => array( - 'deleted_notice_uri_key' => array('uri'), - ), - 'indexes' => array( - 'deleted_notice_profile_id_idx' => array('profile_id'), - ), - ); - } -} diff --git a/classes/Notice.php b/classes/Notice.php index 44d5adb1b7..737f186a10 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -163,34 +163,11 @@ class Notice extends Managed_DataObject if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) { return $this->delete(); } - throw new AuthorizationException('You are not allowed to delete other user\'s notices'); + throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); } - function delete($useWhere=false) + public function delete($useWhere=false) { - // For auditing purposes, save a record that the notice - // was deleted. - - // @fixme we have some cases where things get re-run and so the - // insert fails. - $deleted = Deleted_notice::getKV('id', $this->id); - - if (!$deleted instanceof Deleted_notice) { - $deleted = Deleted_notice::getKV('uri', $this->uri); - } - - if (!$deleted instanceof Deleted_notice) { - $deleted = new Deleted_notice(); - - $deleted->id = $this->id; - $deleted->profile_id = $this->profile_id; - $deleted->uri = $this->uri; - $deleted->created = $this->created; - $deleted->deleted = common_sql_now(); - - $deleted->insert(); - } - if (Event::handle('NoticeDeleteRelated', array($this))) { // Clear related records diff --git a/db/core.php b/db/core.php index d779717fd4..f654d79d99 100644 --- a/db/core.php +++ b/db/core.php @@ -72,7 +72,6 @@ $classes = array('Schema_version', 'Group_block', 'Group_alias', 'Session', - 'Deleted_notice', 'Config', 'Profile_role', 'Location_namespace', diff --git a/lib/default.php b/lib/default.php index 38b8bcb1af..554d3ae63c 100644 --- a/lib/default.php +++ b/lib/default.php @@ -306,6 +306,7 @@ $default = array('core' => array( 'ActivityVerb' => array(), 'ActivityVerbPost' => array(), + 'ActivityModeration' => array(), 'AuthCrypt' => array(), 'Cronish' => array(), 'Favorite' => array(), diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php new file mode 100644 index 0000000000..1494833992 --- /dev/null +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -0,0 +1,118 @@ + + */ +class ActivityModerationPlugin extends ActivityVerbHandlerPlugin +{ + public function tag() + { + return 'actmod'; + } + + public function types() + { + return array(); + } + + public function verbs() + { + return array(ActivityVerb::DELETE); + } + + public function onBeforePluginCheckSchema() + { + Deleted_notice::beforeSchemaUpdate(); + return true; + } + + public function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('deleted_notice', Deleted_notice::schemaDef()); + return true; + } + + protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // FIXME: switch based on action type + return _m('TITLE', 'Notice moderation'); + } + + protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + // pass + } + + protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + switch (true) { + case ActivityUtils::compareVerbs($verb, array(ActivityVerb::DELETE)): + // do whatever preparation is necessary to delete a verb + $target->delete(); + break; + default: + throw new ServerException('ActivityVerb POST not handled by plugin that was supposed to do it.'); + } + } + + public function deleteRelated(Notice $notice) + { + if ($notice->getProfile()->hasRole(Profile_role::DELETED)) { + // Don't save a new Deleted_notice entry if the profile is being deleted + return true; + } + + // For auditing purposes, save a record that the notice was deleted. + return Deleted_notice::addNew($notice); + } + + /** + * This is run when a 'delete' verb activity comes in. + * + * @return boolean hook flag + */ + protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array()) + { + // Let's see if this has been deleted already. + $deleted = Deleted_notice::getKV('uri', $act->id); + if ($deleted instanceof Deleted_notice) { + return $deleted; + } + + common_debug('DELETING notice: ' . $act->objects[0]->id); + $target = Notice::getByUri($act->objects[0]->id); + + $deleted = new Deleted_notice(); + + $deleted->id = $target->getID(); + $deleted->profile_id = $target->getProfile()->getID(); + $deleted->uri = Deleted_notice::newUri($target->getProfile(), $target); + $deleted->act_uri = $target->getUri(); + $deleted->act_created = $target->created; + $deleted->created = common_sql_now(); + + common_debug('DELETING notice, storing Deleted_notice entry'); + $deleted->insert(); + + common_debug('DELETING notice, actually deleting now!'); + $target->delete(); + + return $deleted; + } + + public function activityObjectFromNotice(Notice $notice) + { + $object = Deleted_notice::fromStored($notice); + return $object->asActivityObject(); + } + + protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped) + { + if (!$scoped instanceof Profile || !($scoped->sameAs($target->getProfile()) || $scoped->hasRight(Right::DELETEOTHERSNOTICE))) { + throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); + } + return DeletenoticeForm($action, array('notice'=>$target)); + } +} diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php new file mode 100644 index 0000000000..c450c0eb63 --- /dev/null +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -0,0 +1,224 @@ +. + */ + +if (!defined('GNUSOCIAL')) { exit(1); } + +/** + * Table Definition for deleted_notice + */ + +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(191) unique_key not 255 because utf8mb4 takes more space + public $act_uri; // varchar(191) unique_key not 255 because utf8mb4 takes more space + public $created; // datetime() not_null + public $deleted; // datetime() not_null + + public static function schemaDef() + { + return array( + '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' => 191, 'description' => 'URI of the deleted notice'), + 'act_uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'), + 'act_created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'), + 'created' => array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was deleted'), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'deleted_notice_act_uri_key' => array('act_uri'), + ), + 'indexes' => array( + 'deleted_notice_profile_id_idx' => array('profile_id'), + ), + ); + } + + public static function addNew(Notice $notice) + { + $actor = $notice->getProfile(); + + if ($actor->hasRole(Profile_role::DELETED)) { + // Don't emit notices if the user is deleted + return true; + } + + $act = new Activity(); + $act->type = ActivityObject::ACTIVITY; + $act->verb = ActivityVerb::DELETE; + $act->time = time(); + $act->id = TagURI::mint('deleted_notice:%d:%d:%s', + $actor->getID(), + $notice->getID(), + common_date_iso8601(common_sql_now())); + + $act->content = sprintf(_m('%2$s deleted notice {{%4$s}}.'), + htmlspecialchars($actor->getUrl()), + htmlspecialchars($actor->getBestName()), + htmlspecialchars($notice->getUrl()), + htmlspecialchars($notice->getUri()) + ); + + $act->actor = $actor->asActivityObject(); + $act->target = new ActivityObject(); + $act->target->id = $notice->getUri(); + $act->objects = array(clone($act->target)); + + $url = $notice->getUrl(); + $act->selfLink = $url; + $act->editLink = $url; + + // This will make ActivityModeration run saveObjectFromActivity which adds + // a new Deleted_notice entry in the database as well as deletes the notice + // if the actor has permission to do so. + $stored = Notice::saveActivity($act, $actor); + + return $stored; + } + + static public function fromStored(Notice $stored) + { + $class = get_called_class(); + $object = new $class; + $object->act_uri = $stored->getUri(); + if (!$object->find(true)) { + throw new NoResultException($object); + } + return $object; + } + + public function getActor() + { + return Profile::getByID($this->profile_id); + } + + static public function getObjectType() + { + return 'activity'; + } + + protected $_stored = array(); + + public function getStored() + { + $uri = $this->getTargetUri(); + if (!isset($this->_stored[$uri])) { + $stored = new Notice(); + $stored->uri = $uri; + if (!$stored->find(true)) { + throw new NoResultException($stored); + } + $this->_stored[$uri] = $stored; + } + return $this->_stored[$uri]; + } + + public function getTargetUri() + { + return $this->uri; + } + + public function getUri() + { + return $this->act_uri; + } + + public function asActivityObject(Profile $scoped=null) + { + $actobj = new ActivityObject(); + $actobj->id = $this->getUri(); + $actobj->type = ActivityUtils::resolveUri(self::getObjectType()); + $actobj->actor = $this->getActorObject(); + $actobj->target = new ActivityObject(); + $actobj->target->id = $this->getTargetUri(); + $actobj->objects = array(clone($actobj->target)); + $actobj->verb = ActivityVerb::DELETE; + $actobj->title = ActivityUtils::verbToTitle($actobj->verb); + + $actor = $this->getActor(); + $actobj->content = sprintf(_m('%2$s deleted notice {{%3$s}}.'), + htmlspecialchars($actor->getUrl()), + htmlspecialchars($actor->getBestName()), + htmlspecialchars($actor->getTargetUri()) + ); + + return $actobj; + } + + static function newUri(Profile $actor, Managed_DataObject $object, $created=null) + { + if (is_null($created)) { + $created = common_sql_now(); + } + return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s', + $actor->getID(), + ActivityUtils::resolveUri(self::getObjectType(), true), + $object->getID(), + common_date_iso8601($created)); + } + + static public function beforeSchemaUpdate() + { + $table = strtolower(get_called_class()); + $schema = Schema::get(); + $schemadef = $schema->getTableDef($table); + + // 2015-10-03 We change the meaning of the 'uri' field and move its + // content to the 'act_uri' for the deleted activity. act_created is + // added too. + if (isset($schemadef['fields']['act_uri'])) { + // We already have the act_uri field, so no need to migrate to it. + return; + } + echo "\nFound old $table table, upgrading it to contain 'act_uri' and 'act_created' field..."; + + $schemadef['fields']['act_uri'] = array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'); + $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'); + unset($schemadef['unique keys']); + $schema->ensureTable($table, $schemadef); + + $deleted = new Deleted_notice(); + $result = $deleted->find(); + if ($result === false) { + print "\nFound no deleted_notice entries, continuing..."; + return true; + } + print "\nFound $result deleted_notice entries, aligning with new database layout: "; + while($deleted->fetch()) { + $orig = clone($deleted); + $deleted->act_uri = $deleted->uri; + // this is a fake URI just to have something to put there to avoid NULL + $deleted->uri = TagURI::mint(strtolower(get_called_class()).':%d:%s:%s:%s', + $deleted->profile_id, + ActivityUtils::resolveUri(self::getObjectType(), true), + 'unknown', + common_date_iso8601($deleted->created)); + $deleted->act_created = $deleted->created; // we don't actually know when the notice was created + $deleted->updateWithKeys($orig, 'id'); + print "."; + } + print "DONE.\n"; + print "Resuming core schema upgrade..."; + } + +} diff --git a/lib/deletenoticeform.php b/plugins/ActivityModeration/forms/deletenotice.php similarity index 100% rename from lib/deletenoticeform.php rename to plugins/ActivityModeration/forms/deletenotice.php diff --git a/scripts/upgrade.php b/scripts/upgrade.php index 126ef29036..06a2f74771 100755 --- a/scripts/upgrade.php +++ b/scripts/upgrade.php @@ -88,6 +88,7 @@ function updateSchemaPlugins() { printfnq("Upgrading plugin schema..."); + Event::handle('BeforePluginCheckSchema'); Event::handle('CheckSchema'); printfnq("DONE.\n"); From dac617d95ad98932604c3f07f1c038e25c5669c6 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 12:26:09 +0200 Subject: [PATCH 130/156] I think all the notice deletion calls are event-compatible now This means we can handle DeleteNoticeAsProfile in plugins, such as the ActivityModeration plugin. --- actions/apistatusesdestroy.php | 2 +- actions/apistatusesshow.php | 2 +- actions/deletenotice.php | 2 +- classes/Notice.php | 25 +++++++++++-------- lib/activitymover.php | 2 +- .../ActivityModerationPlugin.php | 16 +++++++++--- .../classes/Deleted_notice.php | 14 ++++++----- plugins/Event/actions/cancelrsvp.php | 2 +- plugins/GNUsocialPhotos/actions/editphoto.php | 2 +- 9 files changed, 41 insertions(+), 26 deletions(-) diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php index db41c87ad0..2d32124c42 100644 --- a/actions/apistatusesdestroy.php +++ b/actions/apistatusesdestroy.php @@ -124,7 +124,7 @@ class ApiStatusesDestroyAction extends ApiAuthAction if ($this->user->id == $this->notice->profile_id) { if (Event::handle('StartDeleteOwnNotice', array($this->user, $this->notice))) { - $this->notice->delete(); + $this->notice->deleteAs($this->scoped); Event::handle('EndDeleteOwnNotice', array($this->user, $this->notice)); } $this->showNotice(); diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php index 8b9cc34779..70b9a9c27a 100644 --- a/actions/apistatusesshow.php +++ b/actions/apistatusesshow.php @@ -236,7 +236,7 @@ class ApiStatusesShowAction extends ApiPrivateAuthAction } if (Event::handle('StartDeleteOwnNotice', array($this->auth_user, $this->notice))) { - $this->notice->delete(); + $this->notice->deleteAs($this->scoped); Event::handle('EndDeleteOwnNotice', array($this->auth_user, $this->notice)); } diff --git a/actions/deletenotice.php b/actions/deletenotice.php index 40b276a348..f0aa767916 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -65,7 +65,7 @@ class DeletenoticeAction extends FormAction { if ($this->arg('yes')) { if (Event::handle('StartDeleteOwnNotice', array($this->scoped->getUser(), $this->notice))) { - $this->notice->delete(); + $this->notice->deleteAs($this->scoped); Event::handle('EndDeleteOwnNotice', array($this->scoped->getUser(), $this->notice)); } } else { diff --git a/classes/Notice.php b/classes/Notice.php index 737f186a10..8f98abd306 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -158,20 +158,14 @@ class Notice extends Managed_DataObject $this->_profile[$this->profile_id] = $profile; } - public function deleteAs(Profile $actor) + public function deleteAs(Profile $actor, $delete_event=true) { - if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) { - return $this->delete(); + if (!$this->getProfile()->sameAs($actor) && !$actor->hasRight(Right::DELETEOTHERSNOTICE)) { + throw new AuthorizationException(_('You are not allowed to delete another user\'s notice.')); } - throw new AuthorizationException(_('You are not allowed to delete other user\'s notices')); - } - public function delete($useWhere=false) - { if (Event::handle('NoticeDeleteRelated', array($this))) { - // Clear related records - $this->clearReplies(); $this->clearLocation(); $this->clearRepeats(); @@ -179,10 +173,21 @@ class Notice extends Managed_DataObject $this->clearGroupInboxes(); $this->clearFiles(); $this->clearAttentions(); - // NOTE: we don't clear queue items } + $result = null; + if (!$delete_event || Event::handle('DeleteNoticeAsProfile', array($this, $actor, &$result))) { + // If $delete_event is true, we run the event. If the Event then + // returns false it is assumed everything was handled properly + // and the notice was deleted. + $result = $this->delete(); + } + return $result; + } + + public function delete($useWhere=false) + { $result = parent::delete($useWhere); $this->blowOnDelete(); diff --git a/lib/activitymover.php b/lib/activitymover.php index ac828d9491..74c5c68ad6 100644 --- a/lib/activitymover.php +++ b/lib/activitymover.php @@ -114,7 +114,7 @@ class ActivityMover extends QueueHandler $sink->postActivity($act); $notice = Notice::getKV('uri', $act->objects[0]->id); if (!empty($notice)) { - $notice->delete(); + $notice->deleteAs($user->getProfile(), false); } break; case ActivityVerb::JOIN: diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php index 1494833992..4422310e58 100644 --- a/plugins/ActivityModeration/ActivityModerationPlugin.php +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -59,13 +59,21 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin public function deleteRelated(Notice $notice) { - if ($notice->getProfile()->hasRole(Profile_role::DELETED)) { - // Don't save a new Deleted_notice entry if the profile is being deleted + // pass + } + + public function onDeleteNoticeAsProfile(Notice $stored, Profile $actor, &$result) { + // By adding a new 'delete' verb we will eventually trigger $this->saveObjectFromActivity + if (false === Deleted_notice::addNew($stored, $actor)) { + // false is returned if we did not have an error, but did not create the object + // (i.e. the author is currently being deleted) return true; } - // For auditing purposes, save a record that the notice was deleted. - return Deleted_notice::addNew($notice); + // We return false (to stop the event) if the deleted_notice entry was + // added, which means we have run $this->saveObjectFromActivity which + // in turn has called the delete function of the notice. + return false; } /** diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index c450c0eb63..790e2ab38d 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -54,13 +54,15 @@ class Deleted_notice extends Managed_DataObject ); } - public static function addNew(Notice $notice) + public static function addNew(Notice $notice, Profile $actor=null) { - $actor = $notice->getProfile(); + if (is_null($actor)) { + $actor = $notice->getProfile(); + } - if ($actor->hasRole(Profile_role::DELETED)) { - // Don't emit notices if the user is deleted - return true; + if ($notice->getProfile()->hasRole(Profile_role::DELETED)) { + // Don't emit notices if the notice author is (being) deleted + return false; } $act = new Activity(); @@ -80,7 +82,7 @@ class Deleted_notice extends Managed_DataObject ); $act->actor = $actor->asActivityObject(); - $act->target = new ActivityObject(); + $act->target = new ActivityObject(); // We don't save the notice object, as it's supposed to be removed! $act->target->id = $notice->getUri(); $act->objects = array(clone($act->target)); diff --git a/plugins/Event/actions/cancelrsvp.php b/plugins/Event/actions/cancelrsvp.php index 1f8e6fde71..83f6a73ae0 100644 --- a/plugins/Event/actions/cancelrsvp.php +++ b/plugins/Event/actions/cancelrsvp.php @@ -138,7 +138,7 @@ class CancelrsvpAction extends Action // NB: this will delete the rsvp, too if (!empty($notice)) { common_log(LOG_DEBUG, "Deleting notice..."); - $notice->delete(); + $notice->deleteAs($this->scoped); } else { common_log(LOG_DEBUG, "Deleting RSVP alone..."); $this->rsvp->delete(); diff --git a/plugins/GNUsocialPhotos/actions/editphoto.php b/plugins/GNUsocialPhotos/actions/editphoto.php index 8f719919f9..35dac7bf3f 100644 --- a/plugins/GNUsocialPhotos/actions/editphoto.php +++ b/plugins/GNUsocialPhotos/actions/editphoto.php @@ -192,7 +192,7 @@ class EditphotoAction extends Action $this->photo->delete(); if (Event::handle('StartDeleteOwnNotice', array($this->user, $notice))) { - $notice->delete(); + $notice->deleteAs($this->scoped); Event::handle('EndDeleteOwnNotice', array($this->user, $notice)); } $this->showForm(_('Success!')); From 2e77a83816725ca87b92f908f7b27b0a32ee7571 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 12:29:22 +0200 Subject: [PATCH 131/156] Store delete verb as object_type=activity --- plugins/ActivityModeration/classes/Deleted_notice.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index 790e2ab38d..e3723a329f 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -149,10 +149,11 @@ class Deleted_notice extends Managed_DataObject { $actobj = new ActivityObject(); $actobj->id = $this->getUri(); - $actobj->type = ActivityUtils::resolveUri(self::getObjectType()); + $actobj->type = ActivityObject::ACTIVITY; $actobj->actor = $this->getActorObject(); $actobj->target = new ActivityObject(); $actobj->target->id = $this->getTargetUri(); + $actobj->target->type = ActivityUtils::resolveUri(self::getObjectType()); $actobj->objects = array(clone($actobj->target)); $actobj->verb = ActivityVerb::DELETE; $actobj->title = ActivityUtils::verbToTitle($actobj->verb); From 1244e8d9297de2675ac3977907f6dfebf2863c9a Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 12:33:33 +0200 Subject: [PATCH 132/156] Somewhat better logging while still in trial phase --- plugins/ActivityModeration/ActivityModerationPlugin.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php index 4422310e58..64e065d305 100644 --- a/plugins/ActivityModeration/ActivityModerationPlugin.php +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -89,8 +89,8 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin return $deleted; } - common_debug('DELETING notice: ' . $act->objects[0]->id); $target = Notice::getByUri($act->objects[0]->id); + common_debug('DELETING notice: ' . $act->objects[0]->id . ' on behalf of profile id==' . $target->getProfile()->getID()); $deleted = new Deleted_notice(); @@ -101,8 +101,10 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin $deleted->act_created = $target->created; $deleted->created = common_sql_now(); - common_debug('DELETING notice, storing Deleted_notice entry'); - $deleted->insert(); + $result = $deleted->insert(); + if ($result === false) { + throw new ServerException('Could not insert Deleted_notice entry into database!'); + } common_debug('DELETING notice, actually deleting now!'); $target->delete(); From aba38d5586cc9c84337a024c611f51e3392dd9cf Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 12:39:23 +0200 Subject: [PATCH 133/156] bump to 1.2.0-beta1 as we have remote delete functionality now --- lib/framework.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/framework.php b/lib/framework.php index 954e29597f..57c775d4c3 100644 --- a/lib/framework.php +++ b/lib/framework.php @@ -23,7 +23,7 @@ define('GNUSOCIAL_ENGINE', 'GNU social'); define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/'); define('GNUSOCIAL_BASE_VERSION', '1.2.0'); -define('GNUSOCIAL_LIFECYCLE', 'alpha2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' +define('GNUSOCIAL_LIFECYCLE', 'beta1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release' define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE); From 66a1d63dd0c9b097ea554709ca50a814368146d6 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 13:24:31 +0200 Subject: [PATCH 134/156] if is_local was not provided to Notice:saveActivity it wouldn't be set --- classes/Notice.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classes/Notice.php b/classes/Notice.php index 8f98abd306..fe42c074a0 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -798,6 +798,8 @@ class Notice extends Managed_DataObject if (!$actor->hasRight(Right::PUBLICNOTICE) || ($source && $autosource && in_array($source, $autosource))) { $stored->is_local = Notice::LOCAL_NONPUBLIC; + } else { + $notice->is_local = $is_local; } // Maybe a missing act-time should be fatal if the actor is not local? From db9f68e651678a0b5dc8a949e311501fca86dd76 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 13:45:06 +0200 Subject: [PATCH 135/156] Sigh, use the correct variable name... --- classes/Notice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Notice.php b/classes/Notice.php index fe42c074a0..7a3dc2bc0f 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -799,7 +799,7 @@ class Notice extends Managed_DataObject ($source && $autosource && in_array($source, $autosource))) { $stored->is_local = Notice::LOCAL_NONPUBLIC; } else { - $notice->is_local = $is_local; + $stored->is_local = $is_local; } // Maybe a missing act-time should be fatal if the actor is not local? From 7c68537b06983293bdc5d0dd1e4ceb8846f2453c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 14:39:49 +0200 Subject: [PATCH 136/156] Deletes now federated. But might not be handled properly --- classes/Notice.php | 6 ++++++ .../ActivityModerationPlugin.php | 21 ++++++++++++++++++- .../classes/Deleted_notice.php | 19 +++++++++++------ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/classes/Notice.php b/classes/Notice.php index 7a3dc2bc0f..1d9823c79c 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -286,6 +286,12 @@ class Notice extends Managed_DataObject : $this->object_type; } + // activity plugins tend to use this function instead, but it's the same + public function getObjectType() + { + return $this->get_object_type(); + } + public static function getByUri($uri) { $notice = new Notice(); diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php index 64e065d305..dc21b11552 100644 --- a/plugins/ActivityModeration/ActivityModerationPlugin.php +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -96,7 +96,7 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin $deleted->id = $target->getID(); $deleted->profile_id = $target->getProfile()->getID(); - $deleted->uri = Deleted_notice::newUri($target->getProfile(), $target); + $deleted->uri = $act->id; $deleted->act_uri = $target->getUri(); $deleted->act_created = $target->created; $deleted->created = common_sql_now(); @@ -112,6 +112,25 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin return $deleted; } + // FIXME: Put this in lib/activityhandlerplugin.php when we're ready + // with the other microapps/activityhandlers as well. + // Also it should be StartNoticeAsActivity (with a prepped Activity, including ->context etc.) + public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null) + { + if (!$this->isMyNotice($stored)) { + return true; + } + + common_debug('Extending activity '.$stored->id.' with '.get_called_class()); + $this->extendActivity($stored, $act, $scoped); + return false; + } + + public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) + { + Deleted_notice::extendActivity($stored, $act, $scoped); + } + public function activityObjectFromNotice(Notice $notice) { $object = Deleted_notice::fromStored($notice); diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index e3723a329f..c4f12b14b0 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -69,10 +69,7 @@ class Deleted_notice extends Managed_DataObject $act->type = ActivityObject::ACTIVITY; $act->verb = ActivityVerb::DELETE; $act->time = time(); - $act->id = TagURI::mint('deleted_notice:%d:%d:%s', - $actor->getID(), - $notice->getID(), - common_date_iso8601(common_sql_now())); + $act->id = self::newUri($actor, $notice); $act->content = sprintf(_m('%2$s deleted notice {{%4$s}}.'), htmlspecialchars($actor->getUrl()), @@ -153,7 +150,6 @@ class Deleted_notice extends Managed_DataObject $actobj->actor = $this->getActorObject(); $actobj->target = new ActivityObject(); $actobj->target->id = $this->getTargetUri(); - $actobj->target->type = ActivityUtils::resolveUri(self::getObjectType()); $actobj->objects = array(clone($actobj->target)); $actobj->verb = ActivityVerb::DELETE; $actobj->title = ActivityUtils::verbToTitle($actobj->verb); @@ -168,6 +164,17 @@ class Deleted_notice extends Managed_DataObject return $actobj; } + static public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) + { + $object = self::fromStored($stored); + + $act->target = $object->asActivityObject(); + $act->objects = array(clone($act->target)); + + $act->context->replyToID = $object->getTargetUri(); + $act->title = ActivityUtils::verbToTitle($act->verb); + } + static function newUri(Profile $actor, Managed_DataObject $object, $created=null) { if (is_null($created)) { @@ -175,7 +182,7 @@ class Deleted_notice extends Managed_DataObject } return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s', $actor->getID(), - ActivityUtils::resolveUri(self::getObjectType(), true), + ActivityUtils::resolveUri($object->getObjectType(), true), $object->getID(), common_date_iso8601($created)); } From db726ca294a5ed4b8760b60c2a5ac064403a6c9c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 15:31:56 +0200 Subject: [PATCH 137/156] Return the correct URI in Deleted_notice --- plugins/ActivityModeration/classes/Deleted_notice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index c4f12b14b0..904088b996 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -134,12 +134,12 @@ class Deleted_notice extends Managed_DataObject public function getTargetUri() { - return $this->uri; + return $this->act_uri; } public function getUri() { - return $this->act_uri; + return $this->uri; } public function asActivityObject(Profile $scoped=null) From e6d7534a88de35fff12d9d61a872dfec6f4a5b4f Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 15:39:06 +0200 Subject: [PATCH 138/156] We haven't created the Deleted_notice yet, just use target URI directly --- plugins/ActivityModeration/classes/Deleted_notice.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index 904088b996..7c58f5b455 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -166,12 +166,11 @@ class Deleted_notice extends Managed_DataObject static public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) { - $object = self::fromStored($stored); - - $act->target = $object->asActivityObject(); + $act->target = new ActivityObject(); + $act->target->id = $stored->getUri(); $act->objects = array(clone($act->target)); - $act->context->replyToID = $object->getTargetUri(); + $act->context->replyToID = $stored->getUri(); $act->title = ActivityUtils::verbToTitle($act->verb); } From 833abbb1916425425173645ab3ddbb25638f420e Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 16:07:19 +0200 Subject: [PATCH 139/156] Fixes to Deleted_notice --- .../classes/Deleted_notice.php | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index 7c58f5b455..f74ea331ee 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -37,7 +37,7 @@ class Deleted_notice extends Managed_DataObject { return array( 'fields' => array( - 'id' => array('type' => 'int', 'not null' => true, 'description' => 'identity of notice'), + 'id' => array('type' => 'int', 'not null' => true, 'description' => 'notice ID'), 'profile_id' => array('type' => 'int', 'not null' => true, 'description' => 'author of the notice'), 'uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the deleted notice'), 'act_uri' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'), @@ -46,6 +46,7 @@ class Deleted_notice extends Managed_DataObject ), 'primary key' => array('id'), 'unique keys' => array( + 'deleted_notice_uri_key' => array('uri'), 'deleted_notice_act_uri_key' => array('act_uri'), ), 'indexes' => array( @@ -99,7 +100,7 @@ class Deleted_notice extends Managed_DataObject { $class = get_called_class(); $object = new $class; - $object->act_uri = $stored->getUri(); + $object->uri = $stored->getUri(); // Lookup by delete activity's URI! (that's what is _stored_ in our db!) if (!$object->find(true)) { throw new NoResultException($object); } @@ -111,6 +112,11 @@ class Deleted_notice extends Managed_DataObject return Profile::getByID($this->profile_id); } + public function getActorObject() + { + return $this->getActor()->asActivityObject(); + } + static public function getObjectType() { return 'activity'; @@ -166,11 +172,14 @@ class Deleted_notice extends Managed_DataObject static public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) { + // the original notice is deleted, but we have stored some important data + $object = self::fromStored($stored); + $act->target = new ActivityObject(); - $act->target->id = $stored->getUri(); + $act->target->id = $object->getTargetUri(); $act->objects = array(clone($act->target)); - $act->context->replyToID = $stored->getUri(); + $act->context->replyToID = $object->getTargetUri(); $act->title = ActivityUtils::verbToTitle($act->verb); } @@ -201,8 +210,8 @@ class Deleted_notice extends Managed_DataObject } echo "\nFound old $table table, upgrading it to contain 'act_uri' and 'act_created' field..."; - $schemadef['fields']['act_uri'] = array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the delete activity, may exist in notice table'); - $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'date the notice record was created'); + $schemadef['fields']['act_uri'] = array('type' => 'varchar', 'not null' => true, 'length' => 191, 'description' => 'URI of the deleted notice'); + $schemadef['fields']['act_created'] = array('type' => 'datetime', 'not null' => true, 'description' => 'datetime the notice record was created'); unset($schemadef['unique keys']); $schema->ensureTable($table, $schemadef); @@ -216,12 +225,14 @@ class Deleted_notice extends Managed_DataObject while($deleted->fetch()) { $orig = clone($deleted); $deleted->act_uri = $deleted->uri; - // this is a fake URI just to have something to put there to avoid NULL - $deleted->uri = TagURI::mint(strtolower(get_called_class()).':%d:%s:%s:%s', + // this is a fake URI just to have something to put there to avoid NULL. crc32 of uri is to avoid collisions + $deleted->uri = TagURI::mint(strtolower(get_called_class()).':%d:%s:%s:%s:crc32=%x', $deleted->profile_id, ActivityUtils::resolveUri(self::getObjectType(), true), 'unknown', - common_date_iso8601($deleted->created)); + common_date_iso8601($deleted->created), + crc32($deleted->act_uri) + ); $deleted->act_created = $deleted->created; // we don't actually know when the notice was created $deleted->updateWithKeys($orig, 'id'); print "."; From aab7667cd53b06c012d811aebb7a391dd3ec74e7 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 16:19:38 +0200 Subject: [PATCH 140/156] Sigh, copy-pasting when tired is a bad idea. --- plugins/ActivityModeration/ActivityModerationPlugin.php | 1 - plugins/ActivityModeration/classes/Deleted_notice.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/ActivityModeration/ActivityModerationPlugin.php b/plugins/ActivityModeration/ActivityModerationPlugin.php index dc21b11552..34735ebf2c 100644 --- a/plugins/ActivityModeration/ActivityModerationPlugin.php +++ b/plugins/ActivityModeration/ActivityModerationPlugin.php @@ -106,7 +106,6 @@ class ActivityModerationPlugin extends ActivityVerbHandlerPlugin throw new ServerException('Could not insert Deleted_notice entry into database!'); } - common_debug('DELETING notice, actually deleting now!'); $target->delete(); return $deleted; diff --git a/plugins/ActivityModeration/classes/Deleted_notice.php b/plugins/ActivityModeration/classes/Deleted_notice.php index f74ea331ee..ff1b56e273 100644 --- a/plugins/ActivityModeration/classes/Deleted_notice.php +++ b/plugins/ActivityModeration/classes/Deleted_notice.php @@ -164,7 +164,7 @@ class Deleted_notice extends Managed_DataObject $actobj->content = sprintf(_m('%2$s deleted notice {{%3$s}}.'), htmlspecialchars($actor->getUrl()), htmlspecialchars($actor->getBestName()), - htmlspecialchars($actor->getTargetUri()) + htmlspecialchars($this->getTargetUri()) ); return $actobj; From 30a4393afab9c426a95da69ecb7805bc00e24de2 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 23:25:02 +0200 Subject: [PATCH 141/156] Move around some code related to Magic_envelope and signing --- plugins/OStatus/lib/magicenvelope.php | 63 +++++++++++++++++++++------ plugins/OStatus/lib/salmon.php | 7 ++- plugins/OStatus/lib/salmonaction.php | 8 +--- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index dfd3abaeab..533cd7d201 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -32,6 +32,8 @@ class MagicEnvelope const NS = 'http://salmon-protocol.org/ns/magic-env'; + protected $actor = null; // Profile of user who has signed the envelope + protected $data = null; // When stored here it is _always_ base64url encoded protected $data_type = null; protected $encoding = null; @@ -48,7 +50,7 @@ class MagicEnvelope * @fixme may give fatal errors if some elements are missing or invalid XML * @fixme calling DOMDocument::loadXML statically triggers warnings in strict mode */ - public function __construct($xml=null) { + public function __construct($xml=null, Profile $actor=null) { if (!empty($xml)) { $dom = new DOMDocument(); if (!$dom->loadXML($xml)) { @@ -56,6 +58,15 @@ class MagicEnvelope } elseif (!$this->fromDom($dom)) { throw new ServerException('Could not load MagicEnvelope from DOM'); } + } elseif ($actor instanceof Profile) { + // So far we only allow setting with _either_ $xml _or_ $actor as that's + // all our circumstances require. But it may be confusing for new developers. + // The idea is that feeding XML must be followed by interpretation and then + // running $magic_env->verify($profile), just as in SalmonAction->prepare(...) + // and supplying an $actor (which right now has to be a User) will require + // defining the $data, $data_type etc. attributes manually afterwards before + // signing the envelope.. + $this->setActor($actor); } } @@ -162,8 +173,21 @@ class MagicEnvelope * * @throws Exception of various kinds on signing failure */ - public function signMessage($text, $mimetype, Magicsig $magicsig) + public function signMessage($text, $mimetype) { + if (!$this->actor instanceof Profile) { + throw new ServerException('No profile to sign message with is set.'); + } elseif (!$this->actor->isLocal()) { + throw new ServerException('Cannot sign magic envelopes with remote users since we have no private key.'); + } + + // Find already stored key + $magicsig = Magicsig::getKV('user_id', $this->actor->getID()); + if (!$magicsig instanceof Magicsig) { + // and if it doesn't exist, it is time to create one! + $magicsig = Magicsig::generate($this->actor->getUser()); + } + assert($magicsig instanceof Magicsig); assert($magicsig->privateKey instanceof Crypt_RSA); // Prepare text and metadata for signing @@ -290,7 +314,12 @@ class MagicEnvelope return false; } - return $magicsig->verify($this->signingText(), $this->getSignature()); + if (!$magicsig->verify($this->signingText(), $this->getSignature())) { + // TRANS: Client error when incoming salmon slap signature does not verify cryptographically. + throw new ClientException(_m('Salmon signature verification failed.')); + } + $this->setActor($profile); + return true; } /** @@ -323,6 +352,22 @@ class MagicEnvelope return true; } + public function setActor(Profile $actor) + { + if ($this->actor instanceof Profile) { + throw new ServerException('Cannot set a new actor profile for MagicEnvelope object.'); + } + $this->actor = $actor; + } + + public function getActor() + { + if (!$this->actor instanceof Profile) { + throw new ServerException('No actor set for this magic envelope.'); + } + return $this->actor; + } + /** * Encode the given string as a signed MagicEnvelope XML document, * using the keypair for the given local user profile. We can of @@ -342,16 +387,8 @@ class MagicEnvelope */ public static function signAsUser($text, User $user) { - // Find already stored key - $magicsig = Magicsig::getKV('user_id', $user->id); - if (!$magicsig instanceof Magicsig) { - $magicsig = Magicsig::generate($user); - } - assert($magicsig instanceof Magicsig); - assert($magicsig->privateKey instanceof Crypt_RSA); - - $magic_env = new MagicEnvelope(); - $magic_env->signMessage($text, 'application/atom+xml', $magicsig); + $magic_env = new MagicEnvelope(null, $user->getProfile()); + $magic_env->signMessage($text, 'application/atom+xml'); return $magic_env; } diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 2097ffa77f..f81c9bdc22 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -55,12 +55,13 @@ class Salmon try { $magic_env = MagicEnvelope::signAsUser($xml, $user); - $envxml = $magic_env->toXML(); } catch (Exception $e) { common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage()); return false; } + $envxml = $magic_env->toXML(); + $headers = array('Content-Type: application/magic-envelope+xml'); try { @@ -73,8 +74,10 @@ class Salmon } // Diaspora wants a slightly different formatting on the POST (other Content-type, so body needs "xml=") + // This also gives us the opportunity to send the specially formatted Diaspora salmon slap, which + // encrypts the content of me:data if ($response->getStatus() === 422) { - common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. Diaspora? Will try again! Body: %s', + common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed, will adapt and try again! Body: %s', $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); $headers = array('Content-Type: application/x-www-form-urlencoded'); $client->setBody('xml=' . Magicsig::base64_url_encode($envxml)); diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 320ea6cdfa..3cb76ca336 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -79,12 +79,8 @@ class SalmonAction extends Action $this->clientError($e->getMessage()); } - // Cryptographic verification test - if (!$magic_env->verify($this->actor)) { - common_log(LOG_DEBUG, "Salmon signature verification failed."); - // TRANS: Client error. - $this->clientError(_m('Salmon signature verification failed.')); - } + // Cryptographic verification test, throws exception on failure + $magic_env->verify($this->actor); return true; } From bc9e3b1843b2b663fae76d13be071769e9413ff5 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 3 Oct 2015 23:51:06 +0200 Subject: [PATCH 142/156] Remove XSS attack vector in JoinListItem --- plugins/Activity/lib/joinlistitem.php | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/plugins/Activity/lib/joinlistitem.php b/plugins/Activity/lib/joinlistitem.php index 6e3dbcf62a..5a481f767a 100644 --- a/plugins/Activity/lib/joinlistitem.php +++ b/plugins/Activity/lib/joinlistitem.php @@ -46,30 +46,4 @@ if (!defined('STATUSNET')) { */ class JoinListItem extends SystemListItem { - function showContent() - { - $notice = $this->nli->notice; - $out = $this->nli->out; - - $mem = Group_member::getKV('uri', $notice->uri); - - if (!empty($mem)) { - $out->elementStart('div', 'join-activity'); - $profile = $mem->getMember(); - $group = $mem->getGroup(); - - // TRANS: Text for "joined list" item in activity plugin. - // TRANS: %1$s is a profile URL, %2$s is a profile name, - // TRANS: %3$s is a group home URL, %4$s is a group name. - $out->raw(sprintf(_m('%2$s joined the group %4$s.'), - $profile->profileurl, - $profile->getBestName(), - $group->homeUrl(), - $group->getBestName())); - - $out->elementEnd('div'); - } else { - parent::showContent(); - } - } } From 184293c6346c8ef0d73d9eb944c90a1799093dfa Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 00:17:07 +0200 Subject: [PATCH 143/156] Break out MagicEnvelope->toXML() functionality to allow for plugin flexibility --- plugins/OStatus/OStatusPlugin.php | 31 +++++++++++++++++++ plugins/OStatus/lib/magicenvelope.php | 44 ++++++++++++++++++++------- plugins/OStatus/lib/salmon.php | 32 ++----------------- 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 0dace39db0..24e877e262 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1351,4 +1351,35 @@ class OStatusPlugin extends Plugin } return true; } + + public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env) + { + $envxml = $magic_env->toXML(); + + $headers = array('Content-Type: application/magic-envelope+xml'); + + try { + $client = new HTTPClient(); + $client->setBody($envxml); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_ERR, "Salmon post to $endpoint_uri failed: " . $e->getMessage()); + return false; + } + if ($response->getStatus() === 422) { + common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed, will adapt and try again if that plugin is enabled!')); + return true; + } + + // 200 OK is the best response + // 202 Accepted is what we get from Diaspora for example + if (!in_array($response->getStatus(), array(200, 202))) { + common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s', + $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); + return true; + } + + // Since we completed the salmon slap, we discontinue the event + return false; + } } diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 533cd7d201..2c54e3679b 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -205,18 +205,25 @@ class MagicEnvelope * * @return string representation of XML document */ - public function toXML() { - $xs = new XMLStringer(); - $xs->startXML(); - $xs->elementStart('me:env', array('xmlns:me' => self::NS)); - $xs->element('me:data', array('type' => $this->data_type), $this->data); - $xs->element('me:encoding', null, $this->encoding); - $xs->element('me:alg', null, $this->alg); - $xs->element('me:sig', null, $this->getSignature()); - $xs->elementEnd('me:env'); + public function toXML($flavour=null) { + $xml = null; + if (Event::handle('MagicEnvelopeToXML', array($this, $flavour, &$xml))) { + // fall back to our default, normal Magic Envelope XML. + $xs = new XMLStringer(); + $xs->startXML(); + $xs->elementStart('me:env', array('xmlns:me' => self::NS)); + $xs->element('me:data', array('type' => $this->data_type), $this->data); + $xs->element('me:encoding', null, $this->encoding); + $xs->element('me:alg', null, $this->alg); + $xs->element('me:sig', null, $this->getSignature()); + $xs->elementEnd('me:env'); - $string = $xs->getString(); - return $string; + $xml = $xs->getString(); + } + if (is_null($xml)) { + throw new ServerException('No Magic Envelope XML string was created.'); + } + return $xml; } /* @@ -265,6 +272,21 @@ class MagicEnvelope return $this->sig; } + public function getSignatureAlgorithm() + { + return $this->alg; + } + + public function getDataType() + { + return $this->data_type; + } + + public function getEncoding() + { + return $this->encoding; + } + /** * Find the author URI referenced in the payload Atom entry. * diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index f81c9bdc22..15ed123eed 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -60,39 +60,11 @@ class Salmon return false; } - $envxml = $magic_env->toXML(); - - $headers = array('Content-Type: application/magic-envelope+xml'); - - try { - $client = new HTTPClient(); - $client->setBody($envxml); - $response = $client->post($endpoint_uri, $headers); - } catch (HTTP_Request2_Exception $e) { - common_log(LOG_ERR, "Salmon post to $endpoint_uri failed: " . $e->getMessage()); + if (Event::handle('SalmonSlap', array($magic_env))) { return false; + //throw new ServerException('Could not distribute salmon slap as no plugin completed the event.'); } - // Diaspora wants a slightly different formatting on the POST (other Content-type, so body needs "xml=") - // This also gives us the opportunity to send the specially formatted Diaspora salmon slap, which - // encrypts the content of me:data - if ($response->getStatus() === 422) { - common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed, will adapt and try again! Body: %s', - $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); - $headers = array('Content-Type: application/x-www-form-urlencoded'); - $client->setBody('xml=' . Magicsig::base64_url_encode($envxml)); - $response = $client->post($endpoint_uri, $headers); - } - - // 200 OK is the best response - // 202 Accepted is what we get from Diaspora for example - if (!in_array($response->getStatus(), array(200, 202))) { - common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s', - $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); - return false; - } - - // Success! return true; } } From 9b461db4da665e2c0d5cfdeda53b56aaef781445 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 09:59:01 +0200 Subject: [PATCH 144/156] Send the entire XMLStringer object in MagicEnvelope events. --- plugins/OStatus/lib/magicenvelope.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index 2c54e3679b..e6b068c924 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -206,11 +206,11 @@ class MagicEnvelope * @return string representation of XML document */ public function toXML($flavour=null) { - $xml = null; - if (Event::handle('MagicEnvelopeToXML', array($this, $flavour, &$xml))) { + $xs = new XMLStringer(); + $xs->startXML(); // header, to point out it's not HTML or anything... + if (Event::handle('StartMagicEnvelopeToXML', array($this, $xs, $flavour))) { // fall back to our default, normal Magic Envelope XML. - $xs = new XMLStringer(); - $xs->startXML(); + // the $xs element _may_ have had elements added, or could get in the end event $xs->elementStart('me:env', array('xmlns:me' => self::NS)); $xs->element('me:data', array('type' => $this->data_type), $this->data); $xs->element('me:encoding', null, $this->encoding); @@ -218,12 +218,9 @@ class MagicEnvelope $xs->element('me:sig', null, $this->getSignature()); $xs->elementEnd('me:env'); - $xml = $xs->getString(); + Event::handle('EndMagicEnvelopeToXML', array($this, $xs, $flavour)); } - if (is_null($xml)) { - throw new ServerException('No Magic Envelope XML string was created.'); - } - return $xml; + return $xs->getString(); } /* From 2aed59a02af83af0021db6464bd3cd3626c09b71 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 12:06:48 +0200 Subject: [PATCH 145/156] Diaspora plugin is almost there (for remote salmon slaps at least) --- lib/util.php | 16 ++- plugins/Diaspora/DiasporaPlugin.php | 161 ++++++++++++++++++++++++++ plugins/OStatus/OStatusPlugin.php | 4 +- plugins/OStatus/lib/magicenvelope.php | 14 ++- plugins/OStatus/lib/salmon.php | 5 +- 5 files changed, 190 insertions(+), 10 deletions(-) diff --git a/lib/util.php b/lib/util.php index edada03864..64733986d3 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1556,14 +1556,24 @@ function common_root_url($ssl=false) return $url; } +/** + * returns $bytes bytes of raw random data + */ +function common_random_rawstr($bytes) +{ + $rawstr = @file_exists('/dev/urandom') + ? common_urandom($bytes) + : common_mtrand($bytes); + + return $rawstr; +} + /** * returns $bytes bytes of random data as a hexadecimal string */ function common_random_hexstr($bytes) { - $str = @file_exists('/dev/urandom') - ? common_urandom($bytes) - : common_mtrand($bytes); + $str = common_random_rawstr($bytes); $hexstr = ''; for ($i = 0; $i < $bytes; $i++) { diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 66a9759f87..68f23a4382 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -30,6 +30,12 @@ if (!defined('GNUSOCIAL')) { exit(1); } * @maintainer Mikael Nordfeldth */ +// Depends on OStatus of course. +addPlugin('OStatus'); + +//Since Magicsig hasn't loaded yet +require_once('Crypt/AES.php'); + class DiasporaPlugin extends Plugin { const REL_SEED_LOCATION = 'http://joindiaspora.com/seed_location'; @@ -62,4 +68,159 @@ class DiasporaPlugin extends Plugin return true; } + + public function onStartMagicEnvelopeToXML(MagicEnvelope $magic_env, XMLStringer $xs, $flavour=null, Profile $target=null) + { + // Since Diaspora doesn't use a separate namespace for their "extended" + // salmon slap, we'll have to resort to this workaround hack. + if ($flavour !== 'diaspora') { + return true; + } + + // WARNING: This changes the $magic_env contents! Be aware of it. + + /** + * https://wiki.diasporafoundation.org/Federation_protocol_overview + * + * Constructing the encryption header + */ + + /** + * Choose an AES key and initialization vector, suitable for the + * aes-256-cbc cipher. I shall refer to this as the “inner key” + * and the “inner initialization vector (iv)”. + */ + $inner_key = new Crypt_AES(CRYPT_AES_MODE_CBC); + $inner_key->setKeyLength(256); // set length to 256 bits (could be calculated, but let's be sure) + $inner_key->setKey(common_random_rawstr(32)); // 32 bytes from a (pseudo) random source + $inner_key->setIV(common_random_rawstr(16)); // 16 bytes is the block length + + /** + * Construct the following XML snippet: + * + * ((base64-encoded inner iv)) + * ((base64-encoded inner key)) + * + * Alice Exampleman + * acct:user@sender.example + * + * + */ + $decrypted_header = sprintf('%1$s%2$s%3$s', + base64_encode($inner_key->iv), + base64_encode($inner_key->key), + $magic_env->getActor()->getAcctUri()); + + /** + * Construct another AES key and initialization vector suitable + * for the aes-256-cbc cipher. I shall refer to this as the + * “outer key” and the “outer initialization vector (iv)”. + */ + $outer_key = new Crypt_AES(CRYPT_AES_MODE_CBC); + $outer_key->setKeyLength(256); // set length to 256 bits (could be calculated, but let's be sure) + $outer_key->setKey(common_random_rawstr(32)); // 32 bytes from a (pseudo) random source + $outer_key->setIV(common_random_rawstr(16)); // 16 bytes is the block length + + /** + * Encrypt your XML snippet using the “outer key” + * and “outer iv” (using the aes-256-cbc cipher). This encrypted + * blob shall be referred to as “the ciphertext”. + */ + $ciphertext = $outer_key->encrypt($decrypted_header); + + /** + * Construct the following JSON object, which shall be referred to + * as “the outer aes key bundle”: + * { + * "iv": ((base64-encoded AES outer iv)), + * "key": ((base64-encoded AES outer key)) + * } + */ + $outer_bundle = json_encode(array( + 'iv' => base64_encode($outer_key->iv), + 'key' => base64_encode($outer_key->key), + )); + /** + * Encrypt the “outer aes key bundle” with Bob’s RSA public key. + * I shall refer to this as the “encrypted outer aes key bundle”. + */ + $key_fetcher = new MagicEnvelope(); + $remote_keys = $key_fetcher->getKeyPair($target, true); // actually just gets the public key + $enc_outer = $remote_keys->publicKey->encrypt($outer_bundle); + + /** + * Construct the following JSON object, which I shall refer to as + * the “encrypted header json object”: + * { + * "aes_key": ((base64-encoded encrypted outer aes key bundle)), + * "ciphertext": ((base64-encoded ciphertextm from above)) + * } + */ + $enc_header = json_encode(array( + 'aes_key' => base64_encode($enc_outer), + 'ciphertext' => base64_encode($ciphertext), + )); + + /** + * Construct the xml snippet: + * ((base64-encoded encrypted header json object)) + */ + $xs->element('encrypted_header', null, base64_encode($enc_header)); + + /** + * In order to prepare the payload message for inclusion in your + * salmon slap, you will: + * + * 1. Encrypt the payload message using the aes-256-cbc cipher and + * the “inner encryption key” and “inner encryption iv” you + * chose earlier. + * 2. Base64-encode the encrypted payload message. + */ + $payload = $inner_key->encrypt($magic_env->getData()); + $magic_env->signMessage(base64_encode($payload), 'application/xml'); + + + // Since we have to change the content of me:data we'll just write the + // whole thing from scratch. We _could_ otherwise have just manipulated + // that element and added the encrypted_header in the EndMagicEnvelopeToXML event. + $xs->elementStart('me:env', array('xmlns:me' => MagicEnvelope::NS)); + $xs->element('me:data', array('type' => $magic_env->getDataType()), $magic_env->getData()); + $xs->element('me:encoding', null, $magic_env->getEncoding()); + $xs->element('me:alg', null, $magic_env->getSignatureAlgorithm()); + $xs->element('me:sig', null, $magic_env->getSignature()); + $xs->elementEnd('me:env'); + + return false; + } + + public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env, Profile $target=null) + { + $envxml = $magic_env->toXML($target, 'diaspora'); + + // Diaspora wants another POST format (base64url-encoded POST variable 'xml') + $headers = array('Content-Type: application/x-www-form-urlencoded'); + + // Another way to distinguish Diaspora from GNU social is that a POST with + // $headers=array('Content-Type: application/magic-envelope+xml') would return + // HTTP status code 422 Unprocessable Entity, at least as of 2015-10-04. + try { + $client = new HTTPClient(); + $client->setBody('xml=' . Magicsig::base64_url_encode($envxml)); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_ERR, "Diaspora-flavoured Salmon post to $endpoint_uri failed: " . $e->getMessage()); + return false; + } + + // 200 OK is the best response + // 202 Accepted is what we get from Diaspora for example + if (!in_array($response->getStatus(), array(200, 202))) { + common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s', + $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); + return true; + } + + // Success! + return false; + } } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 24e877e262..5b7147ebee 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1352,9 +1352,9 @@ class OStatusPlugin extends Plugin return true; } - public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env) + public function onSalmonSlap($endpoint_uri, MagicEnvelope $magic_env, Profile $target=null) { - $envxml = $magic_env->toXML(); + $envxml = $magic_env->toXML($target); $headers = array('Content-Type: application/magic-envelope+xml'); diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index e6b068c924..e96862bbad 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -205,10 +205,10 @@ class MagicEnvelope * * @return string representation of XML document */ - public function toXML($flavour=null) { + public function toXML(Profile $target=null, $flavour=null) { $xs = new XMLStringer(); $xs->startXML(); // header, to point out it's not HTML or anything... - if (Event::handle('StartMagicEnvelopeToXML', array($this, $xs, $flavour))) { + if (Event::handle('StartMagicEnvelopeToXML', array($this, $xs, $flavour, $target))) { // fall back to our default, normal Magic Envelope XML. // the $xs element _may_ have had elements added, or could get in the end event $xs->elementStart('me:env', array('xmlns:me' => self::NS)); @@ -218,7 +218,7 @@ class MagicEnvelope $xs->element('me:sig', null, $this->getSignature()); $xs->elementEnd('me:env'); - Event::handle('EndMagicEnvelopeToXML', array($this, $xs, $flavour)); + Event::handle('EndMagicEnvelopeToXML', array($this, $xs, $flavour, $target)); } return $xs->getString(); } @@ -266,6 +266,9 @@ class MagicEnvelope public function getSignature() { + if (empty($this->sig)) { + throw new ServerException('You must first call signMessage before getSignature'); + } return $this->sig; } @@ -274,6 +277,11 @@ class MagicEnvelope return $this->alg; } + public function getData() + { + return $this->data; + } + public function getDataType() { return $this->data_type; diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index 15ed123eed..cdc8e02d77 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -46,7 +46,7 @@ class Salmon * @param User $user local user profile whose keys we sign with * @return boolean success */ - public static function post($endpoint_uri, $xml, User $user) + public static function post($endpoint_uri, $xml, User $user, Profile $target=null) { if (empty($endpoint_uri)) { common_debug('No endpoint URI for Salmon post to '.$user->getUri()); @@ -60,7 +60,8 @@ class Salmon return false; } - if (Event::handle('SalmonSlap', array($magic_env))) { + // $target is so far only used in Diaspora, so it can be null + if (Event::handle('SalmonSlap', array($endpoint_uri, $magic_env, $target))) { return false; //throw new ServerException('Could not distribute salmon slap as no plugin completed the event.'); } From 684b9419a037109f7a6f169ef3eba81e968733ed Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 14:46:45 +0200 Subject: [PATCH 146/156] Add an event to get plugin discovery hints from XRD --- plugins/OStatus/lib/discoveryhints.php | 43 ++++++++++++++------------ 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/plugins/OStatus/lib/discoveryhints.php b/plugins/OStatus/lib/discoveryhints.php index 27c32b483c..4df7690038 100644 --- a/plugins/OStatus/lib/discoveryhints.php +++ b/plugins/OStatus/lib/discoveryhints.php @@ -24,28 +24,31 @@ class DiscoveryHints { { $hints = array(); - foreach ($xrd->links as $link) { - switch ($link->rel) { - case WebFingerResource_Profile::PROFILEPAGE: - $hints['profileurl'] = $link->href; - break; - case Salmon::REL_SALMON: - case Salmon::NS_MENTIONS: // XXX: deprecated, remove in the future - case Salmon::NS_REPLIES: // XXX: deprecated, remove in the future - $hints['salmon'] = $link->href; - break; - case Discovery::UPDATESFROM: - if (empty($link->type) || $link->type == 'application/atom+xml') { - $hints['feedurl'] = $link->href; + if (Event::handle('StartDiscoveryHintsFromXRD', array($xrd, &$hints))) { + foreach ($xrd->links as $link) { + switch ($link->rel) { + case WebFingerResource_Profile::PROFILEPAGE: + $hints['profileurl'] = $link->href; + break; + case Salmon::REL_SALMON: + case Salmon::NS_MENTIONS: // XXX: deprecated, remove in the future + case Salmon::NS_REPLIES: // XXX: deprecated, remove in the future + $hints['salmon'] = $link->href; + break; + case Discovery::UPDATESFROM: + if (empty($link->type) || $link->type == 'application/atom+xml') { + $hints['feedurl'] = $link->href; + } + break; + case Discovery::HCARD: + case Discovery::MF2_HCARD: + $hints['hcard'] = $link->href; + break; + default: + break; } - break; - case Discovery::HCARD: - case Discovery::MF2_HCARD: - $hints['hcard'] = $link->href; - break; - default: - break; } + Event::handle('EndDiscoveryHintsFromXRD', array($xrd, &$hints)); } return $hints; From 4238875ebe784dfcac2d639ef48cae005923f86d Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 15:57:11 +0200 Subject: [PATCH 147/156] autoloading of Crypt_AES and Crypt_RSA is easier --- plugins/LRDD/LRDDPlugin.php | 1 + plugins/OStatus/OStatusPlugin.php | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/plugins/LRDD/LRDDPlugin.php b/plugins/LRDD/LRDDPlugin.php index 16eee335a3..a7bdb0edbb 100644 --- a/plugins/LRDD/LRDDPlugin.php +++ b/plugins/LRDD/LRDDPlugin.php @@ -41,6 +41,7 @@ class LRDDPlugin extends Plugin return parent::onAutoload($cls); } + public function onStartDiscoveryMethodRegistration(Discovery $disco) { $disco->registerMethod('LRDDMethod_WebFinger'); } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index 5b7147ebee..d1eea869a7 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -83,6 +83,20 @@ class OStatusPlugin extends Plugin return true; } + public function onAutoload($cls) + { + switch ($cls) { + case 'Crypt_AES': + case 'Crypt_RSA': + // Crypt_AES becomes Crypt/AES.php which is found in extlib/phpseclib/ + // which has been added to our include_path before + require_once str_replace('_', '/', $cls) . '.php'; + return false; + } + + return parent::onAutoload($cls); + } + /** * Set up queue handlers for outgoing hub pushes * @param QueueManager $qm From 57f26a97fbe52bcb9ed4e5e2bd5aa7724e8db97b Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 16:40:21 +0200 Subject: [PATCH 148/156] var_export without true --- plugins/OStatus/lib/salmonaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php index 3cb76ca336..a2703f1647 100644 --- a/plugins/OStatus/lib/salmonaction.php +++ b/plugins/OStatus/lib/salmonaction.php @@ -277,7 +277,7 @@ class SalmonAction extends Action function saveNotice() { if (!$this->oprofile instanceof Ostatus_profile) { - common_debug('Ostatus_profile missing in ' . get_class(). ' profile: '.var_export($this->profile)); + common_debug('Ostatus_profile missing in ' . get_class(). ' profile: '.var_export($this->profile, true)); } return $this->oprofile->processPost($this->activity, 'salmon'); } From 2970333adb7b9cc96dfdbd0097bdf3dbe6b43f42 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 16:40:37 +0200 Subject: [PATCH 149/156] Set otherwise undiscovered salmonuri on OStatus script update-profile.php --- plugins/OStatus/lib/feeddiscovery.php | 4 ++++ plugins/OStatus/scripts/update-profile.php | 15 +++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/OStatus/lib/feeddiscovery.php b/plugins/OStatus/lib/feeddiscovery.php index d978cc6745..e9c710bebd 100644 --- a/plugins/OStatus/lib/feeddiscovery.php +++ b/plugins/OStatus/lib/feeddiscovery.php @@ -50,6 +50,10 @@ class FeedSubNoFeedException extends FeedSubException { } +class FeedSubNoSalmonException extends FeedSubException +{ +} + class FeedSubBadXmlException extends FeedSubException { } diff --git a/plugins/OStatus/scripts/update-profile.php b/plugins/OStatus/scripts/update-profile.php index 3d72674920..f20429cd83 100755 --- a/plugins/OStatus/scripts/update-profile.php +++ b/plugins/OStatus/scripts/update-profile.php @@ -52,24 +52,31 @@ showProfile($oprofile); print "\n"; print "Re-running feed discovery for profile URL $oprofile->uri\n"; + +$feedurl = null; +$salmonuri = null; + // @fixme will bork where the URI isn't the profile URL for now $discover = new FeedDiscovery(); try { $feedurl = $discover->discoverFromURL($oprofile->uri); $salmonuri = $discover->getAtomLink(Salmon::REL_SALMON) ?: $discover->getAtomLink(Salmon::NS_REPLIES); // NS_REPLIES is deprecated + if (empty($salmonuri) ) { + throw new FeedSubNoSalmonException('No salmon upstream URI was found'); + } } catch (FeedSubException $e) { $acct = $oprofile->localProfile()->getAcctUri(); - print "Could not discover feeds HTML response, trying reconstructed acct URI: " . $acct; + print "Could not discover feeds HTML response, trying reconstructed acct URI: {$acct}\n"; $disco = new Discovery(); $xrd = $disco->lookup($acct); $hints = DiscoveryHints::fromXRD($xrd); - if (!array_key_exists('feedurl', $hints)) { + if (empty($feedurl) && !array_key_exists('feedurl', $hints)) { throw new FeedSubNoFeedException($acct); } - $feedurl = $hints['feedurl']; - $salmonuri = array_key_exists('salmon', $hints) ? $hints['salmon'] : null; + $feedurl = $feedurl ?: $hints['feedurl']; + $salmonuri = array_key_exists('salmon', $hints) ? $hints['salmon'] : $salmonuri; // get the hub data too and put it in the FeedDiscovery object $discover->discoverFromFeedUrl($feedurl); From f4ed1713974be40d644cdb4378c41c38440b001c Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 17:22:19 +0200 Subject: [PATCH 150/156] Make Magicsig capable of loading public PKCS1 keys --- plugins/OStatus/classes/Magicsig.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php index 890f525862..f5a5733dcb 100644 --- a/plugins/OStatus/classes/Magicsig.php +++ b/plugins/OStatus/classes/Magicsig.php @@ -243,8 +243,8 @@ class Magicsig extends Managed_DataObject * Fill out $this->privateKey or $this->publicKey with a Crypt_RSA object * representing the give key (as mod/exponent pair). * - * @param string $mod base64-encoded - * @param string $exp base64-encoded exponent + * @param string $mod base64url-encoded + * @param string $exp base64url-encoded exponent * @param string $type one of 'public' or 'private' */ public function loadKey($mod, $exp, $type = 'public') @@ -263,6 +263,15 @@ class Magicsig extends Managed_DataObject } } + public function loadPublicKeyPKCS1($key) + { + $rsa = new Crypt_RSA(); + if (!$rsa->setPublicKey($key, CRYPT_RSA_PUBLIC_FORMAT_PKCS1)) { + throw new ServerException('Could not load PKCS1 public key. We probably got this from a remote Diaspora node as the profile public key.'); + } + $this->publicKey = $rsa; + } + /** * Returns the name of the crypto algorithm used for this key. * From 6afa091dca980fcd856b781015ee57afa89c9339 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 17:23:11 +0200 Subject: [PATCH 151/156] Change some Salmon events and similar Use Profile instead of User and (if we know it) send along the target profile, so a Diaspora plugin can encrypt to the receiver. --- plugins/OStatus/classes/Ostatus_profile.php | 7 ++++--- plugins/OStatus/lib/salmon.php | 8 ++++---- plugins/OStatus/lib/salmonqueuehandler.php | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php index 8fc9f6c5f6..dbdbe88523 100644 --- a/plugins/OStatus/classes/Ostatus_profile.php +++ b/plugins/OStatus/classes/Ostatus_profile.php @@ -345,7 +345,7 @@ class Ostatus_profile extends Managed_DataObject $xml = $entry->getString(); common_log(LOG_INFO, "Posting to Salmon endpoint $this->salmonuri: $xml"); - Salmon::post($this->salmonuri, $xml, $actor->getUser()); + Salmon::post($this->salmonuri, $xml, $actor); } /** @@ -359,7 +359,7 @@ class Ostatus_profile extends Managed_DataObject public function notifyActivity($entry, Profile $actor) { if ($this->salmonuri) { - return Salmon::post($this->salmonuri, $this->notifyPrepXml($entry), $actor->getUser()); + return Salmon::post($this->salmonuri, $this->notifyPrepXml($entry), $actor, $this->localProfile()); } common_debug(__CLASS__.' error: No salmonuri for Ostatus_profile uri: '.$this->uri); @@ -378,7 +378,8 @@ class Ostatus_profile extends Managed_DataObject if ($this->salmonuri) { $data = array('salmonuri' => $this->salmonuri, 'entry' => $this->notifyPrepXml($entry), - 'actor' => $actor->id); + 'actor' => $actor->getID(), + 'target' => $this->localProfile()->getID()); $qm = QueueManager::get(); return $qm->enqueue($data, 'salmon'); diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php index cdc8e02d77..b964538cbc 100644 --- a/plugins/OStatus/lib/salmon.php +++ b/plugins/OStatus/lib/salmon.php @@ -43,18 +43,18 @@ class Salmon * * @param string $endpoint_uri * @param string $xml string representation of payload - * @param User $user local user profile whose keys we sign with + * @param Profile $user profile whose keys we sign with (must be a local user) * @return boolean success */ - public static function post($endpoint_uri, $xml, User $user, Profile $target=null) + public static function post($endpoint_uri, $xml, Profile $actor, Profile $target=null) { if (empty($endpoint_uri)) { - common_debug('No endpoint URI for Salmon post to '.$user->getUri()); + common_debug('No endpoint URI for Salmon post to '.$actor->getUri()); return false; } try { - $magic_env = MagicEnvelope::signAsUser($xml, $user); + $magic_env = MagicEnvelope::signAsUser($xml, $actor->getUser()); } catch (Exception $e) { common_log(LOG_ERR, "Salmon unable to sign: " . $e->getMessage()); return false; diff --git a/plugins/OStatus/lib/salmonqueuehandler.php b/plugins/OStatus/lib/salmonqueuehandler.php index 334dc14549..fbc03c3954 100644 --- a/plugins/OStatus/lib/salmonqueuehandler.php +++ b/plugins/OStatus/lib/salmonqueuehandler.php @@ -40,8 +40,9 @@ class SalmonQueueHandler extends QueueHandler assert(is_string($data['entry'])); $actor = Profile::getKV($data['actor']); + $target = array_key_exists('target', $data) ? Profile::getKV($data['target']) : null; - Salmon::post($data['salmonuri'], $data['entry'], $actor->getUser()); + Salmon::post($data['salmonuri'], $data['entry'], $actor, $target); // @fixme detect failure and attempt to resend return true; From af1b0915f48676e25fe0428b26f8987b59abb818 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 17:26:35 +0200 Subject: [PATCH 152/156] Magic signature discovery and envelope changes --- plugins/Diaspora/DiasporaPlugin.php | 26 +++++++++++++++++++- plugins/OStatus/OStatusPlugin.php | 4 +-- plugins/OStatus/lib/magicenvelope.php | 35 +++++++++++++++++++-------- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 68f23a4382..75dba7384a 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -56,6 +56,29 @@ class DiasporaPlugin extends Plugin strtolower($magicsig->toFingerprint())); } + public function onMagicsigPublicKeyFromXRD(XML_XRD $xrd, &$pubkey) + { + // See if we have a Diaspora public key in the XRD response + $link = $xrd->get(self::REL_PUBLIC_KEY, 'RSA'); + if (!is_null($link)) { + // If we do, decode it so we have the PKCS1 format (starts with -----BEGIN PUBLIC KEY-----) + $pkcs1 = base64_decode($link->href); + $magicsig = new Magicsig(Magicsig::DEFAULT_SIGALG); // Diaspora uses RSA-SHA256 (we do too) + try { + // Try to load the public key so we can get it in the standard Magic signature format + $magicsig->loadPublicKeyPKCS1($pkcs1); + // We found it and will now store it in $pubkey in a proper format! + // This is how it would be found in a well implemented XRD according to the standard. + $pubkey = 'data:application/magic-public-key,'.$magicsig->toString(); + common_debug('magic-public-key found in diaspora-public-key: '.$pubkey); + return false; + } catch (ServerException $e) { + common_log(LOG_WARNING, $e->getMessage()); + } + } + return true; + } + public function onPluginVersion(array &$versions) { $versions[] = array('name' => 'Diaspora', @@ -144,6 +167,7 @@ class DiasporaPlugin extends Plugin * Encrypt the “outer aes key bundle” with Bob’s RSA public key. * I shall refer to this as the “encrypted outer aes key bundle”. */ + common_debug('Diaspora creating "outer aes key bundle", will require magic-public-key'); $key_fetcher = new MagicEnvelope(); $remote_keys = $key_fetcher->getKeyPair($target, true); // actually just gets the public key $enc_outer = $remote_keys->publicKey->encrypt($outer_bundle); @@ -216,7 +240,7 @@ class DiasporaPlugin extends Plugin // 202 Accepted is what we get from Diaspora for example if (!in_array($response->getStatus(), array(200, 202))) { common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s', - $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); + $magic_env->getActor()->getID(), $endpoint_uri, $response->getStatus(), $response->getBody())); return true; } diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php index d1eea869a7..86befd58cd 100644 --- a/plugins/OStatus/OStatusPlugin.php +++ b/plugins/OStatus/OStatusPlugin.php @@ -1381,7 +1381,7 @@ class OStatusPlugin extends Plugin return false; } if ($response->getStatus() === 422) { - common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed, will adapt and try again if that plugin is enabled!')); + common_debug(sprintf('Salmon (from profile %d) endpoint %s returned status %s. We assume it is a Diaspora seed; will adapt and try again if that plugin is enabled!', $magic_env->getActor()->getID(), $endpoint_uri, $response->getStatus())); return true; } @@ -1389,7 +1389,7 @@ class OStatusPlugin extends Plugin // 202 Accepted is what we get from Diaspora for example if (!in_array($response->getStatus(), array(200, 202))) { common_log(LOG_ERR, sprintf('Salmon (from profile %d) endpoint %s returned status %s: %s', - $user->id, $endpoint_uri, $response->getStatus(), $response->getBody())); + $magic_env->getActor()->getID(), $endpoint_uri, $response->getStatus(), $response->getBody())); return true; } diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php index e96862bbad..6786bfa298 100644 --- a/plugins/OStatus/lib/magicenvelope.php +++ b/plugins/OStatus/lib/magicenvelope.php @@ -78,14 +78,16 @@ class MagicEnvelope * @param boolean $discovery Network discovery if no local cache? */ public function getKeyPair(Profile $profile, $discovery=false) { - $magicsig = Magicsig::getKV('user_id', $profile->id); + if (!$profile->isLocal()) common_debug('Getting magic-public-key for non-local profile id=='.$profile->getID()); + $magicsig = Magicsig::getKV('user_id', $profile->getID()); if ($discovery && !$magicsig instanceof Magicsig) { + if (!$profile->isLocal()) common_debug('magic-public-key not found, will do discovery for profile id=='.$profile->getID()); // Throws exception on failure, but does not try to _load_ the keypair string. $keypair = $this->discoverKeyPair($profile); $magicsig = new Magicsig(); - $magicsig->user_id = $profile->id; + $magicsig->user_id = $profile->getID(); $magicsig->importKeys($keypair); // save the public key for this profile in our database. // TODO: If the profile generates a new key remotely, we must be able to replace @@ -113,28 +115,41 @@ class MagicEnvelope { $signer_uri = $profile->getUri(); if (empty($signer_uri)) { - throw new ServerException(sprintf('Profile missing URI (id==%d)', $profile->id)); + throw new ServerException(sprintf('Profile missing URI (id==%d)', $profile->getID())); } $disco = new Discovery(); // Throws exception on lookup problems - $xrd = $disco->lookup($signer_uri); + try { + $xrd = $disco->lookup($signer_uri); + } catch (Exception $e) { + // Diaspora seems to require us to request the acct: uri + $xrd = $disco->lookup($profile->getAcctUri()); + } - $link = $xrd->get(Magicsig::PUBLICKEYREL); - if (is_null($link)) { - // TRANS: Exception. - throw new Exception(_m('Unable to locate signer public key.')); + common_debug('Will try to find magic-public-key from XRD of profile id=='.$profile->getID()); + $pubkey = null; + if (Event::handle('MagicsigPublicKeyFromXRD', array($xrd, &$pubkey))) { + $link = $xrd->get(Magicsig::PUBLICKEYREL); + if (is_null($link)) { + // TRANS: Exception. + throw new Exception(_m('Unable to locate signer public key.')); + } + $pubkey = $link->href; + } + if (empty($pubkey)) { + throw new ServerException('Empty Magicsig public key. A bug?'); } // We have a public key element, let's hope it has proper key data. $keypair = false; - $parts = explode(',', $link->href); + $parts = explode(',', $pubkey); if (count($parts) == 2) { $keypair = $parts[1]; } else { // Backwards compatibility check for separator bug in 0.9.0 - $parts = explode(';', $link->href); + $parts = explode(';', $pubkey); if (count($parts) == 2) { $keypair = $parts[1]; } From ec19661312efc9d5200b6464faf91bca84898fe0 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sun, 4 Oct 2015 22:31:07 +0200 Subject: [PATCH 153/156] Can't use return-value in empty() --- classes/Profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Profile.php b/classes/Profile.php index 25a41bee72..49addfe47f 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -144,7 +144,7 @@ class Profile extends Managed_DataObject public function hasPassword() { try { - return !empty($this->getUser()->hasPassword()); + return $this->getUser()->hasPassword(); } catch (NoSuchUserException $e) { return false; } From 3902dc963a4e5bcae97aa236e5982aadb0568490 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 5 Oct 2015 11:22:23 +0200 Subject: [PATCH 154/156] Diaspora encloses magic envelope in ??!!?! --- plugins/Diaspora/DiasporaPlugin.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 75dba7384a..6f955fadcf 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -108,6 +108,9 @@ class DiasporaPlugin extends Plugin * Constructing the encryption header */ + // For some reason it's supposed to be inside an + $xs->elementStart('entry', array('xmlns'=>'http://www.w3.org/2005/Atom')); + /** * Choose an AES key and initialization vector, suitable for the * aes-256-cbc cipher. I shall refer to this as the “inner key” @@ -214,6 +217,8 @@ class DiasporaPlugin extends Plugin $xs->element('me:sig', null, $magic_env->getSignature()); $xs->elementEnd('me:env'); + $xs->elementEnd('entry'); + return false; } From ee6096cca82cc39055c521bd575f522c78e05c99 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 5 Oct 2015 11:28:33 +0200 Subject: [PATCH 155/156] FIXME: Diaspora has its own salmon slap semantics --- plugins/Diaspora/DiasporaPlugin.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 6f955fadcf..616323250f 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -204,6 +204,9 @@ class DiasporaPlugin extends Plugin * 2. Base64-encode the encrypted payload message. */ $payload = $inner_key->encrypt($magic_env->getData()); + //FIXME: This means we don't actually put an in the payload, + // since Diaspora has its own update method! Silly me. Read up on: + // https://wiki.diasporafoundation.org/Federation_Message_Semantics $magic_env->signMessage(base64_encode($payload), 'application/xml'); From 180958185fb6abe075fb89ad87420c9e348f82b1 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Mon, 5 Oct 2015 12:49:54 +0200 Subject: [PATCH 156/156] Found newer Diaspora protocol specifications http://www.rubydoc.info/github/Raven24/diaspora-federation/master/DiasporaFederation/Salmon/EncryptedSlap --- plugins/Diaspora/DiasporaPlugin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/Diaspora/DiasporaPlugin.php b/plugins/Diaspora/DiasporaPlugin.php index 616323250f..376ad098b2 100644 --- a/plugins/Diaspora/DiasporaPlugin.php +++ b/plugins/Diaspora/DiasporaPlugin.php @@ -104,12 +104,13 @@ class DiasporaPlugin extends Plugin /** * https://wiki.diasporafoundation.org/Federation_protocol_overview + * http://www.rubydoc.info/github/Raven24/diaspora-federation/master/DiasporaFederation/Salmon/EncryptedSlap * * Constructing the encryption header */ - // For some reason it's supposed to be inside an - $xs->elementStart('entry', array('xmlns'=>'http://www.w3.org/2005/Atom')); + // For some reason diaspora wants the salmon slap in a header. + $xs->elementStart('diaspora', array('xmlns'=>'https://joindiaspora.com/protocol')); /** * Choose an AES key and initialization vector, suitable for the